├── LICENSE ├── README.md ├── composer.json ├── pint.json └── src ├── PreflightMiddleware.php └── PreflightServiceProvider.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Boris Lepikhin. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Momentum Preflight 2 | 3 | Momentum Preflight is a Laravel package that lets you implement realtime backend-driven request validation for Inertia apps. 4 | 5 | Validate form requests using Inertia form helper just like you already do, without running controllers' code. 6 | 7 | - [**Advanced Inertia**](#advanced-inertia) 8 | - [**Installation**](#installation) 9 | - [**Laravel**](#laravel) 10 | - [**Vue 3**](#vue-3) 11 | - [**Usage**](#usage) 12 | - [**Momentum**](#momentum) 13 | 14 | ## Installation 15 | 16 | ### Laravel 17 | 18 | Install the package into your Laravel app. 19 | 20 | ```bash 21 | composer require based/momentum-preflight 22 | ``` 23 | 24 | Register the `PreflightMiddleware` middleware. 25 | 26 | ```php 27 | middleware(PreflightMiddleware::class); 31 | ``` 32 | 33 | ### Vue 3 34 | 35 | > The frontend package is only for Vue 3 now due to its wide adoption within the Laravel community. 36 | 37 | Install the [frontend package](https://github.com/lepikhinb/momentum-preflight-plugin). 38 | 39 | ```bash 40 | npm i momentum-preflight 41 | # or 42 | yarn add momentum-preflight 43 | ``` 44 | 45 | ## Usage 46 | 47 | Preflight works well with both [FormRequests](https://laravel.com/docs/9.x/validation#form-request-validation) and [Laravel Data](https://spatie.be/docs/laravel-data/v2/as-a-data-transfer-object/request-to-data-object). Due to the simplicity of the approach, it doesn't support the inline `$request->validate(...)` method. 48 | 49 | ```php 50 | form.data(), 71 | () => validate() 72 | ); 73 | ``` 74 | 75 | The package performs validation for all defined rules. However, you can specify exact fields so that all errors don't appear together once you start typing. 76 | 77 | ```vue 78 | 86 | 87 | 94 | ``` 95 | 96 | ## Advanced Inertia 97 | 98 | [](https://advanced-inertia.com) 99 | 100 | Take your Inertia.js skills to the next level with my book [Advanced Inertia](https://advanced-inertia.com/). 101 | Learn advanced concepts and make apps with Laravel and Inertia.js a breeze to build and maintain. 102 | 103 | ## Momentum 104 | 105 | Momentum is a set of packages designed to improve your experience building Inertia-powered apps. 106 | 107 | - [Modal](https://github.com/lepikhinb/momentum-modal) — Build dynamic modal dialogs for Inertia apps 108 | - [Preflight](https://github.com/lepikhinb/momentum-preflight) — Realtime backend-driven validation for Inertia apps 109 | - [Paginator](https://github.com/lepikhinb/momentum-paginator) — Headless wrapper around Laravel Pagination 110 | - [Trail](https://github.com/lepikhinb/momentum-trail) — Frontend package to use Laravel routes with Inertia 111 | - [Lock](https://github.com/lepikhinb/momentum-lock) — Frontend package to use Laravel permissions with Inertia 112 | - [Layout](https://github.com/lepikhinb/momentum-layout) — Persistent layouts for Vue 3 apps 113 | - [Vite Plugin Watch](https://github.com/lepikhinb/vite-plugin-watch) — Vite plugin to run shell commands on file changes 114 | 115 | ## Credits 116 | 117 | - [Boris Lepikhin](https://twitter.com/lepikhinb) 118 | - [All Contributors](../../contributors) 119 | 120 | ## License 121 | 122 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 123 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "based/momentum-preflight", 3 | "description": "Realtime backend-driven validation for Inertia-powered Laravel apps", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Boris Lepikhin", 9 | "email": "boris@lepikhin.com" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "Momentum\\Preflight\\": "src/", 15 | "Momentum\\Preflight\\Tests\\": "tests/" 16 | } 17 | }, 18 | "autoload-dev": { 19 | "psr-4": { 20 | "Momentum\\Preflight\\Tests\\": "tests/" 21 | } 22 | }, 23 | "require": { 24 | "php": "^8.0", 25 | "illuminate/support": "^8.24|^9.0|^10.0|^11.0", 26 | "inertiajs/inertia-laravel": "^0.6.3|^1.0" 27 | }, 28 | "require-dev": { 29 | "orchestra/testbench": "^6.9|^7.0|^8.0", 30 | "nunomaduro/larastan": "^1.0|^2.0", 31 | "pestphp/pest": "^1.22|^2.0", 32 | "pestphp/pest-plugin-laravel": "^1.3|^2.0", 33 | "laravel/pint": "^1.0", 34 | "spatie/laravel-data": "^2.0" 35 | }, 36 | "extra": { 37 | "laravel": { 38 | "providers": [ 39 | "Momentum\\Preflight\\PreflightServiceProvider" 40 | ] 41 | } 42 | }, 43 | "minimum-stability": "dev", 44 | "prefer-stable": true, 45 | "config": { 46 | "allow-plugins": { 47 | "pestphp/pest-plugin": true 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "method_chaining_indentation": true, 5 | "no_superfluous_phpdoc_tags": true, 6 | "cast_spaces": true, 7 | "concat_space": { 8 | "spacing": "one" 9 | }, 10 | "declare_strict_types": false, 11 | "heredoc_to_nowdoc": true, 12 | "constant_case": true, 13 | "no_spaces_around_offset": true, 14 | "no_unneeded_control_parentheses": true, 15 | "general_phpdoc_tag_rename": true, 16 | "phpdoc_summary": true, 17 | "psr_autoloading": true, 18 | "self_accessor": true, 19 | "no_unused_imports": true, 20 | "standardize_not_equals": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/PreflightMiddleware.php: -------------------------------------------------------------------------------- 1 | header('X-Inertia-Preflight') || $request->isMethod('GET')) { 23 | return $next($request); 24 | } 25 | 26 | /** @var \Illuminate\Routing\Route $route */ 27 | $route = app('router')->getCurrentRoute(); 28 | 29 | if ($route->getAction('uses') instanceof Closure) { 30 | $method = new ReflectionFunction($route->getAction('uses')); 31 | } else { 32 | $controller = new ReflectionClass($route->getController()); 33 | 34 | $method = $controller->getMethod($route->getActionMethod()); 35 | } 36 | 37 | $requestClass = $this->getRequestClass($method); 38 | 39 | $this->validate($requestClass); 40 | 41 | throw ValidationException::withMessages([]); 42 | } 43 | 44 | protected function getRequestClass(ReflectionFunction|ReflectionMethod $method): string 45 | { 46 | /** @var ReflectionParameter */ 47 | $parameter = collect($method->getParameters()) 48 | ->filter(fn (ReflectionParameter $parameter) => $parameter->hasType()) 49 | ->first(function (ReflectionParameter $parameter) { 50 | // @phpstan-ignore-next-line 51 | $reflection = new ReflectionClass($parameter->getType()->getName()); 52 | 53 | return $reflection->isSubclassOf(FormRequest::class) 54 | || $reflection->isSubclassOf(Data::class); 55 | }); 56 | 57 | // @phpstan-ignore-next-line 58 | return $parameter->getType()->getName(); 59 | } 60 | 61 | protected function validate(string $requestClass): void 62 | { 63 | $request = app($requestClass); 64 | 65 | // @phpstan-ignore-next-line 66 | if ($request instanceof Data) { 67 | $request->validate($request->all()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/PreflightServiceProvider.php: -------------------------------------------------------------------------------- 1 |