diff --git a/docs/advanced-usage/validation-attributes.md b/docs/advanced-usage/validation-attributes.md index 66692a99..4c6c4304 100644 --- a/docs/advanced-usage/validation-attributes.md +++ b/docs/advanced-usage/validation-attributes.md @@ -20,13 +20,16 @@ class SongData extends Data ## Creating your validation attribute -A validation attribute is a class that extends `ValidationRule` and returns an array of validation rules when the `getRules` method is called: +It is possible to create your own validation attribute by extending the `CustomValidationAttribute` class, this class has a `getRules` method that returns the rules that should be applied to the property. ```php -#[Attribute(Attribute::TARGET_PROPERTY)] -class CustomRule extends ValidationRule +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)] +class CustomRule extends CustomValidationAttribute { - public function getRules(): array + /** + * @return array|object|string + */ + public function getRules(ValidationPath $path): array|object|string; { return [new CustomRule()]; } diff --git a/src/Attributes/Validation/CustomValidationAttribute.php b/src/Attributes/Validation/CustomValidationAttribute.php new file mode 100644 index 00000000..81a977de --- /dev/null +++ b/src/Attributes/Validation/CustomValidationAttribute.php @@ -0,0 +1,14 @@ +|object|string + */ + abstract public function getRules(ValidationPath $path): array|object|string; +} diff --git a/src/RuleInferrers/AttributesRuleInferrer.php b/src/RuleInferrers/AttributesRuleInferrer.php index dd0d104c..b3cf7e07 100644 --- a/src/RuleInferrers/AttributesRuleInferrer.php +++ b/src/RuleInferrers/AttributesRuleInferrer.php @@ -2,6 +2,7 @@ namespace Spatie\LaravelData\RuleInferrers; +use Spatie\LaravelData\Attributes\Validation\CustomValidationAttribute; use Spatie\LaravelData\Attributes\Validation\Present; use Spatie\LaravelData\Support\DataProperty; use Spatie\LaravelData\Support\Validation\PropertyRules; diff --git a/src/Support/Validation/RuleDenormalizer.php b/src/Support/Validation/RuleDenormalizer.php index 5280348f..823a6347 100644 --- a/src/Support/Validation/RuleDenormalizer.php +++ b/src/Support/Validation/RuleDenormalizer.php @@ -8,6 +8,7 @@ use Illuminate\Contracts\Validation\Rule as RuleContract; use Illuminate\Support\Arr; use Illuminate\Support\Str; +use Spatie\LaravelData\Attributes\Validation\CustomValidationAttribute; use Spatie\LaravelData\Attributes\Validation\ObjectValidationAttribute; use Spatie\LaravelData\Attributes\Validation\Rule; use Spatie\LaravelData\Attributes\Validation\StringValidationAttribute; @@ -38,6 +39,10 @@ public function execute(mixed $rule, ValidationPath $path): array return [$rule->getRule($path)]; } + if($rule instanceof CustomValidationAttribute){ + return Arr::wrap($rule->getRules($path)); + } + if ($rule instanceof Rule) { return $this->execute($rule->get(), $path); } diff --git a/tests/Fakes/ValidationAttributes/PassThroughCustomValidationAttribute.php b/tests/Fakes/ValidationAttributes/PassThroughCustomValidationAttribute.php new file mode 100644 index 00000000..59cb2b6a --- /dev/null +++ b/tests/Fakes/ValidationAttributes/PassThroughCustomValidationAttribute.php @@ -0,0 +1,24 @@ +|object|string + */ + public function getRules(ValidationPath $path): array|object|string + { + return $this->rules; + } +} diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index f8b0074b..01466eec 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -9,14 +9,10 @@ use Illuminate\Support\Facades\Validator as ValidatorFacade; use Illuminate\Validation\Rules\Enum; use Illuminate\Validation\Rules\Exists as LaravelExists; +use Illuminate\Validation\Rules\In as LaravelIn; use Illuminate\Validation\ValidationException; use Illuminate\Validation\Validator; - -use function Pest\Laravel\mock; -use function PHPUnit\Framework\assertFalse; - use Spatie\LaravelData\Attributes\DataCollectionOf; - use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapName; use Spatie\LaravelData\Attributes\Validation\ArrayType; @@ -66,7 +62,10 @@ use Spatie\LaravelData\Tests\Fakes\SimpleDataWithExplicitValidationRuleAttributeData; use Spatie\LaravelData\Tests\Fakes\SimpleDataWithOverwrittenRules; use Spatie\LaravelData\Tests\Fakes\Support\FakeInjectable; +use Spatie\LaravelData\Tests\Fakes\ValidationAttributes\PassThroughCustomValidationAttribute; use Spatie\LaravelData\Tests\TestSupport\DataValidationAsserter; +use function Pest\Laravel\mock; +use function PHPUnit\Framework\assertFalse; it('can validate a string', function () { $dataClass = new class () extends Data { @@ -2316,3 +2315,44 @@ public static function rules(ValidationContext $context): array 'array' => ['array', 'present'], ], []); }); + +it('supports custom validation attributes', function () { + $dataClass = new class () extends Data { + #[PassThroughCustomValidationAttribute(['url'])] + public string $url; + }; + + DataValidationAsserter::for($dataClass) + ->assertOk(['url' => 'https://spatie.be']) + ->assertErrors(['url' => 'nowp']) + ->assertRules([ + 'url' => ['required', 'string', 'url'], + ], []); + + $dataClass = new class () extends Data { + #[PassThroughCustomValidationAttribute(['url', 'max:20'])] + public string $url; + }; + + DataValidationAsserter::for($dataClass) + ->assertOk(['url' => 'https://spatie.be']) + ->assertErrors(['url' => 'nowp']) + ->assertErrors(['url' => 'https://rubenvanassche.com']) + ->assertRules([ + 'url' => ['required', 'string', 'url', 'max:20'], + ], []); + + $dataClass = new class () extends Data { + #[PassThroughCustomValidationAttribute([new LaravelIn(['a', 'b'])])] + public string $something; + }; + + + DataValidationAsserter::for($dataClass) + ->assertOk(['something' => 'a']) + ->assertOk(['something' => 'b']) + ->assertErrors(['something' => 'c']) + ->assertRules([ + 'something' => ['required', 'string', new LaravelIn(['a', 'b'])], + ], []); +});