https://github.com/craftcms/laravel-ruleset-validation
Validate requests and objects with reusable Laravel rulesets.
https://github.com/craftcms/laravel-ruleset-validation
Last synced: about 12 hours ago
JSON representation
Validate requests and objects with reusable Laravel rulesets.
- Host: GitHub
- URL: https://github.com/craftcms/laravel-ruleset-validation
- Owner: craftcms
- License: mit
- Created: 2026-04-20T08:15:49.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-20T13:50:55.000Z (2 months ago)
- Last Synced: 2026-04-20T15:36:03.677Z (2 months ago)
- Language: PHP
- Size: 52.7 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Laravel Ruleset Validation
[](https://packagist.org/packages/craftcms/laravel-ruleset-validation)
[](https://github.com/craftcms/laravel-ruleset-validation/actions?query=workflow%3Arun-tests+branch%3Amain)
[](https://github.com/craftcms/laravel-ruleset-validation/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain)
[](https://packagist.org/packages/craftcms/laravel-ruleset-validation)
Move validation out of controllers, form requests, and ad hoc validators into reusable ruleset objects.
This package provides a `Ruleset` base class that can validate either:
- any object implementing `ValidatesWithRuleset`
- a regular `Illuminate\Http\Request`
That makes it useful for DTOs, actions, domain objects, and controllers that want `FormRequest`-style validation without requiring a dedicated `FormRequest` subclass for every workflow.
## Installation
Install the package via Composer:
```bash
composer require craftcms/laravel-ruleset-validation
```
The package uses Laravel package auto-discovery, so no manual service provider registration is required.
## If You Already Know Form Requests
For request-backed validation, this package intentionally follows Laravel's `FormRequest` validation model as closely as possible.
Read the Laravel documentation for the validation lifecycle, authorization, hooks, validated input, redirects, error bags, and Precognition:
[Laravel Form Request Validation](https://laravel.com/docs/13.x/validation#form-request-validation)
[Laravel Working With Validated Input](https://laravel.com/docs/13.x/validation#working-with-validated-input)
If you know how to write a `FormRequest`, you already know how to write a `Ruleset`.
## What This Package Adds
The main difference from `FormRequest` is not the validation API, but where the validation logic can live.
### 1. You can attach a ruleset to any object
Create a class that implements `ValidatesWithRuleset`, add the `HasRuleset` trait, then point it at a ruleset.
```php
$this->title,
'slug' => $this->slug,
'body' => $this->body,
];
}
}
```
Then define the ruleset:
```php
['required', 'string', 'max:255'],
'slug' => ['nullable', 'string', 'max:255'],
'body' => ['nullable', 'string'],
];
}
}
```
Validate the object anywhere in your application:
```php
$data = new CreatePostData(
title: 'Rulesets are nice',
slug: 'rulesets-are-nice',
body: '...',
);
$validated = $data->ruleset->validate();
```
You can also validate only a subset of attributes:
```php
$validated = $data->ruleset
->only(['title', 'slug'])
->validate();
```
And if you need a non-throwing flow for object-backed validation:
```php
$ruleset = $data->ruleset->only(['title']);
if ($ruleset->fails()) {
$validator = $ruleset->getValidator();
// Inspect $validator->errors()
}
```
### 2. You can inject a ruleset directly for a request
If you want `FormRequest` behavior without a `FormRequest` subclass, type-hint the ruleset in your controller action and let Laravel resolve it.
```php
['required', 'string', 'max:255'],
'body' => ['nullable', 'string'],
];
}
}
```
```php
use App\Rulesets\StorePostRuleset;
class PostController
{
public function store(StorePostRuleset $ruleset)
{
$validated = $ruleset->validate();
// ...
}
}
```
If you need to construct one manually, you can instantiate the ruleset directly:
```php
$ruleset = new StorePostRuleset(subject: $request);
```
### 3. You can reuse one ruleset across multiple contexts via scenarios
Rulesets include a lightweight scenario API that `FormRequest` does not provide.
```php
use Illuminate\Validation\Rule;
class PostRuleset extends Ruleset
{
public const SCENARIO_DRAFT = 'draft';
public function rules(): array
{
return [
'title' => [
Rule::requiredIf($this->inScenarios(self::SCENARIO_DRAFT)),
'string',
'max:255'
],
];
}
}
```
```php
$validated = $data->ruleset
->useScenario(PostRuleset::SCENARIO_DRAFT)
->validate();
```
If you only want to switch scenarios for one operation, use `withScenario()`. The previous scenario is restored after the callback returns or throws.
```php
$validated = $data->ruleset->withScenario(
PostRuleset::SCENARIO_DRAFT,
fn () => $data->ruleset->validate(),
);
```
Available helpers:
- `useScenario(string $scenario): static`
- `withScenario(string $scenario, Closure $callback): mixed`
- `getScenario(): string`
- `inScenarios(string ...$scenarios): bool`
When injecting a ruleset into a controller, you can also set the scenario with a parameter attribute:
```php
use App\Rulesets\PostRuleset;
use CraftCms\RulesetValidation\Attributes\Scenario;
class PostController
{
public function storeDraft(#[Scenario(PostRuleset::SCENARIO_DRAFT)] PostRuleset $ruleset)
{
$validated = $ruleset->validate();
// ...
}
}
```
## Form Request Compatibility Notes
When a ruleset is validating an `Illuminate\Http\Request`, the Laravel docs above apply directly to the following features:
- `rules()`
- `authorize()`
- `messages()`
- `attributes()`
- `prepareForValidation()`
- `withValidator()`
- `after()`
- `validated()`
- `safe()`
- `#[StopOnFirstFailure]`
- `#[RedirectTo]`
- `#[RedirectToRoute]`
- `#[ErrorBag]`
- Precognition support
This package mirrors that behavior, with a few small differences:
- `validate()` returns the validated payload directly, like `$request->validate(...)`.
- `only(...)` returns a scoped clone that validates only a subset of attributes, such as `$ruleset->only('title')->validate()`.
- `passes()`, `fails()`, and `getValidator()` are available when you want a non-throwing validation flow.
- Request-backed rulesets may be injected directly into controller actions.
- For object-backed validation, data comes from `validationData()` instead of request input.
- Rulesets can be selected with either the `#[Ruleset(...)]` attribute or a `ruleset(): string` method on the validatable object.
## Choosing A Ruleset For An Object
You can associate a validatable object with a ruleset in two ways.
### Using the `#[Ruleset]` attribute
```php
use App\Rulesets\CreatePostRuleset;
use CraftCms\RulesetValidation\Attributes\Ruleset;
#[Ruleset(CreatePostRuleset::class)]
class CreatePostData implements ValidatesWithRuleset
{
// ...
}
```
### Using a `ruleset()` method
```php
use App\Rulesets\AdminPostRuleset;
use App\Rulesets\CreatePostRuleset;
use CraftCms\RulesetValidation\Concerns\HasRuleset;
use CraftCms\RulesetValidation\Contracts\ValidatesWithRuleset;
class CreatePostData implements ValidatesWithRuleset
{
use HasRuleset;
public function __construct(
public bool $isAdmin,
public string $title,
) {}
public function validationData(): array
{
return [
'title' => $this->title,
];
}
public function ruleset(): string
{
return $this->isAdmin
? AdminPostRuleset::class
: CreatePostRuleset::class;
}
}
```
## Testing
```bash
composer test
```
```bash
composer analyse
```
```bash
composer format
```
## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
## Contributing
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
## Security vulnerabilities
Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
## Credits
- [Pixel & Tonic](https://github.com/craftcms)
- [All Contributors](../../contributors)
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.