Skip to content

Commit

Permalink
Add support for custom validation attribute rules
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Sep 15, 2023
1 parent 935cab6 commit 53d04d8
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 9 deletions.
11 changes: 7 additions & 4 deletions docs/advanced-usage/validation-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>|object|string
*/
public function getRules(ValidationPath $path): array|object|string;
{
return [new CustomRule()];
}
Expand Down
14 changes: 14 additions & 0 deletions src/Attributes/Validation/CustomValidationAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Spatie\LaravelData\Attributes\Validation;

use Spatie\LaravelData\Support\Validation\ValidationPath;
use Spatie\LaravelData\Support\Validation\ValidationRule;

abstract class CustomValidationAttribute extends ValidationRule
{
/**
* @return array<object|string>|object|string
*/
abstract public function getRules(ValidationPath $path): array|object|string;
}
1 change: 1 addition & 0 deletions src/RuleInferrers/AttributesRuleInferrer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/Support/Validation/RuleDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Spatie\LaravelData\Tests\Fakes\ValidationAttributes;

use Attribute;
use Spatie\LaravelData\Attributes\Validation\CustomValidationAttribute;
use Spatie\LaravelData\Support\Validation\ValidationPath;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
class PassThroughCustomValidationAttribute extends CustomValidationAttribute
{
public function __construct(
private array|object|string $rules,
) {
}

/**
* @return array<object|string>|object|string
*/
public function getRules(ValidationPath $path): array|object|string
{
return $this->rules;
}
}
50 changes: 45 additions & 5 deletions tests/ValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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'])],
], []);
});

0 comments on commit 53d04d8

Please sign in to comment.