{"id":51171864,"url":"https://github.com/craftcms/laravel-ruleset-validation","last_synced_at":"2026-06-27T01:08:55.166Z","repository":{"id":352642949,"uuid":"1215754867","full_name":"craftcms/laravel-ruleset-validation","owner":"craftcms","description":"Validate requests and objects with reusable Laravel rulesets.","archived":false,"fork":false,"pushed_at":"2026-04-20T13:50:55.000Z","size":54,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T15:36:03.677Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/craftcms.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T08:15:49.000Z","updated_at":"2026-04-20T13:49:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/craftcms/laravel-ruleset-validation","commit_stats":null,"previous_names":["craftcms/laravel-ruleset-validation"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/craftcms/laravel-ruleset-validation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftcms%2Flaravel-ruleset-validation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftcms%2Flaravel-ruleset-validation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftcms%2Flaravel-ruleset-validation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftcms%2Flaravel-ruleset-validation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/craftcms","download_url":"https://codeload.github.com/craftcms/laravel-ruleset-validation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftcms%2Flaravel-ruleset-validation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34838074,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-27T01:08:50.535Z","updated_at":"2026-06-27T01:08:55.150Z","avatar_url":"https://github.com/craftcms.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laravel Ruleset Validation\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/craftcms/laravel-ruleset-validation.svg?style=flat-square)](https://packagist.org/packages/craftcms/laravel-ruleset-validation)\n[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/craftcms/laravel-ruleset-validation/run-tests.yml?branch=main\u0026label=tests\u0026style=flat-square)](https://github.com/craftcms/laravel-ruleset-validation/actions?query=workflow%3Arun-tests+branch%3Amain)\n[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/craftcms/laravel-ruleset-validation/fix-php-code-style-issues.yml?branch=main\u0026label=code%20style\u0026style=flat-square)](https://github.com/craftcms/laravel-ruleset-validation/actions?query=workflow%3A\"Fix+PHP+code+style+issues\"+branch%3Amain)\n[![Total Downloads](https://img.shields.io/packagist/dt/craftcms/laravel-ruleset-validation.svg?style=flat-square)](https://packagist.org/packages/craftcms/laravel-ruleset-validation)\n\nMove validation out of controllers, form requests, and ad hoc validators into reusable ruleset objects.\n\nThis package provides a `Ruleset` base class that can validate either:\n\n- any object implementing `ValidatesWithRuleset`\n- a regular `Illuminate\\Http\\Request`\n\nThat makes it useful for DTOs, actions, domain objects, and controllers that want `FormRequest`-style validation without requiring a dedicated `FormRequest` subclass for every workflow.\n\n## Installation\n\nInstall the package via Composer:\n\n```bash\ncomposer require craftcms/laravel-ruleset-validation\n```\n\nThe package uses Laravel package auto-discovery, so no manual service provider registration is required.\n\n## If You Already Know Form Requests\n\nFor request-backed validation, this package intentionally follows Laravel's `FormRequest` validation model as closely as possible.\n\nRead the Laravel documentation for the validation lifecycle, authorization, hooks, validated input, redirects, error bags, and Precognition:\n\n[Laravel Form Request Validation](https://laravel.com/docs/13.x/validation#form-request-validation)\n\n[Laravel Working With Validated Input](https://laravel.com/docs/13.x/validation#working-with-validated-input)\n\nIf you know how to write a `FormRequest`, you already know how to write a `Ruleset`.\n\n## What This Package Adds\n\nThe main difference from `FormRequest` is not the validation API, but where the validation logic can live.\n\n### 1. You can attach a ruleset to any object\n\nCreate a class that implements `ValidatesWithRuleset`, add the `HasRuleset` trait, then point it at a ruleset.\n\n```php\n\u003c?php\n\nnamespace App\\Data;\n\nuse App\\Rulesets\\CreatePostRuleset;\nuse CraftCms\\RulesetValidation\\Attributes\\Ruleset;\nuse CraftCms\\RulesetValidation\\Concerns\\HasRuleset;\nuse CraftCms\\RulesetValidation\\Contracts\\ValidatesWithRuleset;\n\n#[Ruleset(CreatePostRuleset::class)]\nclass CreatePostData implements ValidatesWithRuleset\n{\n    use HasRuleset;\n\n    public function __construct(\n        public string $title,\n        public ?string $slug = null,\n        public ?string $body = null,\n    ) {}\n\n    public function validationData(): array\n    {\n        return [\n            'title' =\u003e $this-\u003etitle,\n            'slug' =\u003e $this-\u003eslug,\n            'body' =\u003e $this-\u003ebody,\n        ];\n    }\n}\n```\n\nThen define the ruleset:\n\n```php\n\u003c?php\n\nnamespace App\\Rulesets;\n\nuse CraftCms\\RulesetValidation\\Ruleset;\n\nclass CreatePostRuleset extends Ruleset\n{\n    public function rules(): array\n    {\n        return [\n            'title' =\u003e ['required', 'string', 'max:255'],\n            'slug' =\u003e ['nullable', 'string', 'max:255'],\n            'body' =\u003e ['nullable', 'string'],\n        ];\n    }\n}\n```\n\nValidate the object anywhere in your application:\n\n```php\n$data = new CreatePostData(\n    title: 'Rulesets are nice',\n    slug: 'rulesets-are-nice',\n    body: '...',\n);\n\n$validated = $data-\u003eruleset-\u003evalidate();\n```\n\nYou can also validate only a subset of attributes:\n\n```php\n$validated = $data-\u003eruleset\n    -\u003eonly(['title', 'slug'])\n    -\u003evalidate();\n```\n\nAnd if you need a non-throwing flow for object-backed validation:\n\n```php\n$ruleset = $data-\u003eruleset-\u003eonly(['title']);\n\nif ($ruleset-\u003efails()) {\n    $validator = $ruleset-\u003egetValidator();\n\n    // Inspect $validator-\u003eerrors()\n}\n```\n\n### 2. You can inject a ruleset directly for a request\n\nIf you want `FormRequest` behavior without a `FormRequest` subclass, type-hint the ruleset in your controller action and let Laravel resolve it.\n\n```php\n\u003c?php\n\nnamespace App\\Rulesets;\n\nuse CraftCms\\RulesetValidation\\Ruleset;\n\nclass StorePostRuleset extends Ruleset\n{\n    public function rules(): array\n    {\n        return [\n            'title' =\u003e ['required', 'string', 'max:255'],\n            'body' =\u003e ['nullable', 'string'],\n        ];\n    }\n}\n```\n\n```php\nuse App\\Rulesets\\StorePostRuleset;\n\nclass PostController\n{\n    public function store(StorePostRuleset $ruleset)\n    {\n        $validated = $ruleset-\u003evalidate();\n\n        // ...\n    }\n}\n```\n\nIf you need to construct one manually, you can instantiate the ruleset directly:\n\n```php\n$ruleset = new StorePostRuleset(subject: $request);\n```\n\n### 3. You can reuse one ruleset across multiple contexts via scenarios\n\nRulesets include a lightweight scenario API that `FormRequest` does not provide.\n\n```php\nuse Illuminate\\Validation\\Rule;\n\nclass PostRuleset extends Ruleset\n{\n    public const SCENARIO_DRAFT = 'draft';\n    \n    public function rules(): array\n    {\n        return [\n            'title' =\u003e [\n                Rule::requiredIf($this-\u003einScenarios(self::SCENARIO_DRAFT)),\n                'string',\n                'max:255'\n            ],\n        ];\n    }\n}\n```\n\n```php\n$validated = $data-\u003eruleset\n    -\u003euseScenario(PostRuleset::SCENARIO_DRAFT)\n    -\u003evalidate();\n```\n\nIf you only want to switch scenarios for one operation, use `withScenario()`. The previous scenario is restored after the callback returns or throws.\n\n```php\n$validated = $data-\u003eruleset-\u003ewithScenario(\n    PostRuleset::SCENARIO_DRAFT,\n    fn () =\u003e $data-\u003eruleset-\u003evalidate(),\n);\n```\n\nAvailable helpers:\n\n- `useScenario(string $scenario): static`\n- `withScenario(string $scenario, Closure $callback): mixed`\n- `getScenario(): string`\n- `inScenarios(string ...$scenarios): bool`\n\nWhen injecting a ruleset into a controller, you can also set the scenario with a parameter attribute:\n\n```php\nuse App\\Rulesets\\PostRuleset;\nuse CraftCms\\RulesetValidation\\Attributes\\Scenario;\n\nclass PostController\n{\n    public function storeDraft(#[Scenario(PostRuleset::SCENARIO_DRAFT)] PostRuleset $ruleset)\n    {\n        $validated = $ruleset-\u003evalidate();\n\n        // ...\n    }\n}\n```\n\n## Form Request Compatibility Notes\n\nWhen a ruleset is validating an `Illuminate\\Http\\Request`, the Laravel docs above apply directly to the following features:\n\n- `rules()`\n- `authorize()`\n- `messages()`\n- `attributes()`\n- `prepareForValidation()`\n- `withValidator()`\n- `after()`\n- `validated()`\n- `safe()`\n- `#[StopOnFirstFailure]`\n- `#[RedirectTo]`\n- `#[RedirectToRoute]`\n- `#[ErrorBag]`\n- Precognition support\n\nThis package mirrors that behavior, with a few small differences:\n\n- `validate()` returns the validated payload directly, like `$request-\u003evalidate(...)`.\n- `only(...)` returns a scoped clone that validates only a subset of attributes, such as `$ruleset-\u003eonly('title')-\u003evalidate()`.\n- `passes()`, `fails()`, and `getValidator()` are available when you want a non-throwing validation flow.\n- Request-backed rulesets may be injected directly into controller actions.\n- For object-backed validation, data comes from `validationData()` instead of request input.\n- Rulesets can be selected with either the `#[Ruleset(...)]` attribute or a `ruleset(): string` method on the validatable object.\n\n## Choosing A Ruleset For An Object\n\nYou can associate a validatable object with a ruleset in two ways.\n\n### Using the `#[Ruleset]` attribute\n\n```php\nuse App\\Rulesets\\CreatePostRuleset;\nuse CraftCms\\RulesetValidation\\Attributes\\Ruleset;\n\n#[Ruleset(CreatePostRuleset::class)]\nclass CreatePostData implements ValidatesWithRuleset\n{\n    // ...\n}\n```\n\n### Using a `ruleset()` method\n\n```php\nuse App\\Rulesets\\AdminPostRuleset;\nuse App\\Rulesets\\CreatePostRuleset;\nuse CraftCms\\RulesetValidation\\Concerns\\HasRuleset;\nuse CraftCms\\RulesetValidation\\Contracts\\ValidatesWithRuleset;\n\nclass CreatePostData implements ValidatesWithRuleset\n{\n    use HasRuleset;\n\n    public function __construct(\n        public bool $isAdmin,\n        public string $title,\n    ) {}\n\n    public function validationData(): array\n    {\n        return [\n            'title' =\u003e $this-\u003etitle,\n        ];\n    }\n\n    public function ruleset(): string\n    {\n        return $this-\u003eisAdmin\n            ? AdminPostRuleset::class\n            : CreatePostRuleset::class;\n    }\n}\n```\n\n## Testing\n\n```bash\ncomposer test\n```\n\n```bash\ncomposer analyse\n```\n\n```bash\ncomposer format\n```\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n## Security vulnerabilities\n\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## Credits\n\n- [Pixel \u0026 Tonic](https://github.com/craftcms)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcraftcms%2Flaravel-ruleset-validation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcraftcms%2Flaravel-ruleset-validation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcraftcms%2Flaravel-ruleset-validation/lists"}