It is also possible to write rules down manually in a dedicated method on the data object. This can come in handy when you want
to construct a custom rule object which isn't possible with attributes:
class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {
}
public static function rules(): array
{
return [
'title' => ['required', 'string'],
'artist' => ['required', 'string'],
];
}
}
By overwriting a property's rules within the rules
method, no other rules will be inferred automatically anymore for that property.
This means that in the following example, only a max:20
rule will be added, and not a string
and required
rule:
class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {
}
public static function rules(): array
{
return [
'title' => ['max:20'],
'artist' => ['max:20'],
];
}
}
[
'title' => ['max:20'],
'artist' => ['max:20'],
]
As a rule of thumb always follow these rules:
Always use the array syntax for defining rules and not a single string which spits the rules by | characters.
This is needed when using regexes those | can be seen as part of the regex
##Merging manual rules
Writing manual rules doesn't mean that you can't use the automatic rules inferring anymore. By adding the MergeValidationRules
attribute to your data class, the rules will be merged:
#[MergeValidationRules]
class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {
}
public static function rules(): array
{
return [
'title' => ['max:20'],
'artist' => ['max:20'],
];
}
}
[
'title' => [required, 'string', 'max:20'],
'artist' => [required, 'string', 'max:20'],
]
##Using attributes
It is even possible to use the validationAttribute objects within the rules
method:
class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {
}
public static function rules(): array
{
return [
'title' => [new Required(), new StringType()],
'artist' => [new Required(), new StringType()],
];
}
}
You can even add dependencies to be automatically injected:
use SongSettingsRepository;
class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {
}
public static function rules(SongSettingsRepository $settings): array
{
return [
'title' => [new RequiredIf($settings->forUser(auth()->user())->title_required), new StringType()],
'artist' => [new Required(), new StringType()],
];
}
}
##Using context
Sometimes a bit more context is required, in such a case a ValidationContext
parameter can be injected as such:
Additionally, if you need to access the data payload, you can use $payload
parameter:
class SongData extends Data
{
public function __construct(
public string $title,
public string $artist,
) {
}
public static function rules(ValidationContext $context): array
{
return [
'title' => ['required'],
'artist' => Rule::requiredIf($context->fullPayload['title'] !== 'Never Gonna Give You Up'),
];
}
}
By default, the provided payload is the whole request payload provided to the data object.
If you want to generate rules in nested data objects, then a relative payload can be more useful:
class AlbumData extends Data
{
public function __construct(
public string $title,
public array $songs,
) {
}
}
class SongData extends Data
{
public function __construct(
public string $title,
public ?string $artist,
) {
}
public static function rules(ValidationContext $context): array
{
return [
'title' => ['required'],
'artist' => Rule::requiredIf($context->payload['title'] !== 'Never Gonna Give You Up'),
];
}
}
When providing such a payload:
[
'title' => 'Best songs ever made',
'songs' => [
['title' => 'Never Gonna Give You Up'],
['title' => 'Heroes', 'artist' => 'David Bowie'],
],
];
The rules will be:
[
'title' => ['string', 'required'],
'songs' => ['present', 'array'],
'songs.*.title' => ['string', 'required'],
'songs.*.artist' => ['string', 'nullable'],
'songs.*' => [NestedRules(...)],
]
It is also possible to retrieve the current path in the data object chain we're generating rules for right now by calling $context->path
. In the case of our previous example this would be songs.0
and songs.1
;
Make sure the name of the parameter is $context
in the rules
method, otherwise no context will be injected.