1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Yiisoft\Validator\RulesProvider; |
||
6 | |||
7 | use ReflectionProperty; |
||
8 | use Yiisoft\Validator\DataSet\ObjectDataSet; |
||
9 | use Yiisoft\Validator\Helper\ObjectParser; |
||
10 | use Yiisoft\Validator\RulesProviderInterface; |
||
11 | use Yiisoft\Validator\ValidatorInterface; |
||
12 | |||
13 | /** |
||
14 | * A rules provider that extracts rules from PHP attributes (attached to class properties and class itself). The |
||
15 | * attributes introduced in PHP 8 simplify rules' configuration process, especially for nested data and relations. This |
||
16 | * way the validated structures can be presented as DTO classes with references to each other. |
||
17 | * |
||
18 | * An example of object with both one-to-one (requires PHP > 8.0) and one-to-many (requires PHP > 8.1) relations: |
||
19 | * |
||
20 | * ```php |
||
21 | 16 | * final class Post |
|
22 | * { |
||
23 | * #[Length(max: 255)] |
||
24 | * public string $title = ''; |
||
25 | * |
||
26 | * #[Nested] |
||
27 | * public Author|null $author = null; |
||
28 | * |
||
29 | * // Passing instances is available only since PHP 8.1. |
||
30 | * #[Each(new Nested(File::class))] |
||
31 | * public array $files = []; |
||
32 | * |
||
33 | * public function __construct() |
||
34 | * { |
||
35 | 16 | * $this->author = new Author(); |
|
36 | * } |
||
37 | 16 | * } |
|
38 | 16 | * |
|
39 | * final class Author |
||
40 | * { |
||
41 | 16 | * #[Length(min: 1)] |
|
42 | * public string $name = ''; |
||
43 | * } |
||
44 | * |
||
45 | * // Some rules, like "Nested" can be also configured through the class attribute. |
||
46 | * |
||
47 | 16 | * #[Nested(['url' => new Url()])] |
|
48 | * final class File |
||
49 | 16 | * { |
|
50 | 7 | * public string $url = ''; |
|
51 | 9 | * } |
|
52 | * |
||
53 | 16 | * $post = new Post(title: 'Yii3 Overview 3', author: 'Dmitriy'); |
|
54 | 16 | * $parser = new ObjectParser($post); |
|
55 | 1 | * $rules = $parser->getRules(); |
|
56 | * ``` |
||
57 | 15 | * |
|
58 | 15 | * The `$rules` will contain: |
|
59 | 15 | * |
|
60 | 1 | * ```php |
|
61 | * [ |
||
62 | * new Nested([ |
||
63 | 15 | * 'title' => [new Length(max: 255)], |
|
64 | * 'author' => new Nested([ |
||
65 | * 'name' => [new Length(min: 1)], |
||
66 | * ]), |
||
67 | * 'files' => new Each(new Nested([ |
||
68 | * 'url' => [new Url()], |
||
69 | * ])), |
||
70 | 15 | * ]); |
|
71 | * ]; |
||
72 | 15 | * ``` |
|
73 | 15 | * |
|
74 | * A class name string is valid as a source too: |
||
75 | * |
||
76 | * ```php |
||
77 | * $parser = new ObjectParser(Post::class); |
||
78 | * $rules = $parser->getRules(); // The result is the same as in previous example. |
||
79 | * ``` |
||
80 | * |
||
81 | * Please refer to the guide for more examples. |
||
82 | * |
||
83 | * Note that the rule attributes can be combined with others without affecting parsing. Which properties to parse can be |
||
84 | * configured via `$propertyVisibility` and `$skipStaticProperties` constructor arguments. |
||
85 | * |
||
86 | * Uses {@see ObjectParser} and supports caching. |
||
87 | * |
||
88 | * If you want to additionally extract data or to be able to disable cache, use {@see ObjectDataSet} or |
||
89 | * {@see ObjectParser} directly instead. |
||
90 | * |
||
91 | * @link https://www.php.net/manual/en/language.attributes.overview.php |
||
92 | * |
||
93 | * @psalm-import-type RawRulesMap from ValidatorInterface |
||
94 | */ |
||
95 | final class AttributesRulesProvider implements RulesProviderInterface |
||
96 | { |
||
97 | /** |
||
98 | * @var ObjectParser An object parser instance used to parse rules from attributes. |
||
99 | */ |
||
100 | private ObjectParser $parser; |
||
101 | |||
102 | /** |
||
103 | * @param class-string|object $source A source for parsing rules. Can be either a class name string or an instance. |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||
104 | * @param int $propertyVisibility Visibility levels the properties with rules must have. For example: public and |
||
105 | * protected only, this means that the rest (private ones) will be skipped. Defaults to all visibility levels |
||
106 | * (public, protected and private). |
||
107 | * @param bool $skipStaticProperties Whether the properties with "static" modifier must be skipped. |
||
108 | * |
||
109 | * @psalm-param int-mask-of<ReflectionProperty::IS_*> $propertyVisibility |
||
110 | */ |
||
111 | public function __construct( |
||
112 | string|object $source, |
||
113 | int $propertyVisibility = ReflectionProperty::IS_PRIVATE |
||
114 | | ReflectionProperty::IS_PROTECTED |
||
115 | | ReflectionProperty::IS_PUBLIC, |
||
116 | bool $skipStaticProperties = false, |
||
117 | ) { |
||
118 | $this->parser = new ObjectParser($source, $propertyVisibility, $skipStaticProperties); |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * Returns rules parsed from attributes attached to class properties and class itself. Repetitive calls utilize |
||
123 | * cache. |
||
124 | * |
||
125 | * @return iterable The resulting rules is an array with the following structure: |
||
126 | * |
||
127 | * ```php |
||
128 | * [ |
||
129 | * [new AtLeast(['name', 'author'])], // Rules not bound to a specific attribute. |
||
130 | * 'files' => [new Count(max: 3)], // Attribute specific rules. |
||
131 | * ], |
||
132 | * ``` |
||
133 | * |
||
134 | * @psalm-return RawRulesMap |
||
135 | */ |
||
136 | public function getRules(): iterable |
||
137 | { |
||
138 | return $this->parser->getRules(); |
||
139 | } |
||
140 | } |
||
141 |