Skip to content

Commit 71f520d

Browse files
committed
Merge branch 'docs-8'
2 parents 2b5edd1 + 6fc2a50 commit 71f520d

14 files changed

Lines changed: 463 additions & 87 deletions

src/AttributeTranslator/ArrayAttributeTranslator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Yiisoft\Validator\AttributeTranslatorInterface;
88

99
/**
10-
* An attributes translator that used array of translations for translate attribute name.
10+
* An attribute translator that uses array of translations.
1111
*/
1212
final class ArrayAttributeTranslator implements AttributeTranslatorInterface
1313
{

src/AttributeTranslator/NullAttributeTranslator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Yiisoft\Validator\AttributeTranslatorInterface;
88

99
/**
10-
* An attributes translator that returns attribute name as is.
10+
* An attribute translator that returns attribute name as is.
1111
*/
1212
final class NullAttributeTranslator implements AttributeTranslatorInterface
1313
{

src/AttributeTranslator/TranslatorAttributeTranslator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use Yiisoft\Validator\AttributeTranslatorInterface;
99

1010
/**
11-
* An attributes translator that used {@see TranslatorInterface} for translation.
11+
* An attribute translator that uses {@see TranslatorInterface}.
1212
*/
1313
final class TranslatorAttributeTranslator implements AttributeTranslatorInterface
1414
{

src/AttributeTranslatorInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Yiisoft\Validator;
66

77
/**
8-
* An interface for translates an attributes names.
8+
* An interface for translations of attributes names.
99
*/
1010
interface AttributeTranslatorInterface
1111
{

src/AttributeTranslatorProviderInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Yiisoft\Validator;
66

77
/**
8-
* Provides implementation of {@see AttributeTranslatorInterface} for translate attribute names.
8+
* Provides implementation of {@see AttributeTranslatorInterface} for translations of attribute names.
99
*/
1010
interface AttributeTranslatorProviderInterface
1111
{

src/DataSet/ArrayDataSet.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function __construct(
3535
* Returns an attribute value by its name.
3636
*
3737
* Note that in case of non-existing attribute a default `null` value is returned. If you need to check the presence
38-
* of attribute or return a different default value, use {@see hasAttribute} instead.
38+
* of attribute or return a different default value, use {@see hasAttribute()} instead.
3939
*
4040
* @param string $attribute Attribute name.
4141
*

src/DataSet/ObjectDataSet.php

Lines changed: 194 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,170 @@
99
use Yiisoft\Validator\AttributeTranslatorProviderInterface;
1010
use Yiisoft\Validator\DataSetInterface;
1111
use Yiisoft\Validator\Helper\ObjectParser;
12+
use Yiisoft\Validator\RuleInterface;
13+
use Yiisoft\Validator\RulesProvider\AttributesRulesProvider;
1214
use Yiisoft\Validator\RulesProviderInterface;
1315

1416
/**
15-
* This data set makes use of attributes introduced in PHP 8. It simplifies rules configuration process, especially for
16-
* nested data and relations. Please refer to the guide for examples.
17+
* A data set for object data. The object passed to this data set can provide rules and data by implementing
18+
* {@see RuleInterface} and {@see DataSetInterface}. Alternatively this data set allows getting rules from PHP
19+
* attributes (attached to class properties and class itself) and data from object properties.
20+
*
21+
* An example of object implementing {@see RuleInterface}:
22+
*
23+
* ```php
24+
* final class Author implements RulesProviderInterface
25+
* {
26+
* public function getRules(): iterable
27+
* {
28+
* return ['age' => [new Number(min: 18)]];
29+
* }
30+
* }
31+
* ```
32+
*
33+
* An example of object implementing {@see DataSetInterface}:
34+
*
35+
* ```php
36+
* final class Author implements DataSetInterface
37+
* {
38+
* public function getAttributeValue(string $attribute): mixed
39+
* {
40+
* return $this->getData()[$attribute] ?? null;
41+
* }
42+
*
43+
* public function getData(): mixed
44+
* {
45+
* return ['name' => 'John', 'age' => 18];
46+
* }
47+
*
48+
* public function hasAttribute(string $attribute): bool
49+
* {
50+
* return array_key_exists($attribute, $this->getData());
51+
* }
52+
* }
53+
* ```
54+
*
55+
* These two can be combined and used at the same time.
56+
*
57+
* The attributes introduced in PHP 8 simplify rules' configuration process, especially for nested data and relations.
58+
* This way the validated structures can be presented as DTO classes with references to each other.
59+
*
60+
* An example of DTO with both one-to-one (requires PHP > 8.0) and one-to-many (requires PHP > 8.1) relations:
61+
*
62+
* ```php
63+
* final class Post
64+
* {
65+
* #[HasLength(max: 255)]
66+
* public string $title = '';
67+
*
68+
* #[Nested]
69+
* public Author|null $author = null;
70+
*
71+
* // Passing instances is available only since PHP 8.1.
72+
* #[Each(new Nested([File::class])]
73+
* public array $files = [];
74+
*
75+
* public function __construct()
76+
* {
77+
* $this->author = new Author();
78+
* }
79+
* }
80+
*
81+
* final class Author
82+
* {
83+
* #[HasLength(min: 1)]
84+
* public string $name = '';
85+
* }
86+
*
87+
* // Some rules, like "Nested" can be also configured through the class attribute.
88+
*
89+
* #[Nested(['url' => new Url()])]
90+
* final class File
91+
* {
92+
* public string $url = '';
93+
* }
94+
*
95+
* $post = new Post(title: 'Yii3 Overview 3', author: 'Dmitriy');
96+
* $parser = new ObjectParser($post);
97+
* $rules = $parser->getRules();
98+
* $data = $parser->getData();
99+
* ```
100+
*
101+
* The `$rules` will contain:
102+
*
103+
* ```
104+
* [
105+
* new Nested([
106+
* 'title' => [new HasLength(max: 255)],
107+
* 'author' => new Nested([
108+
* 'name' => [new HasLength(min: 1)],
109+
* ]),
110+
* 'files' => new Each(new Nested([
111+
* 'url' => [new Url()],
112+
* ])),
113+
* ]);
114+
* ];
115+
* ```
116+
*
117+
* And the result of `$data` will be:
118+
*
119+
* ```php
120+
* [
121+
* 'title' => 'Yii3 Overview 3',
122+
* 'author' => 'John',
123+
* 'files' => [],
124+
* ];
125+
* ```
126+
*
127+
* Note that the rule attributes can be combined with others without affecting parsing. Which properties to parse can be
128+
* configured via {@see ObjectDataSet::$propertyVisibility} and {@see ObjectDataSet::$skipStaticProperties} options.
129+
*
130+
* The other combinations of rules / data are also possible, for example: the data is provided by implementing
131+
* {@see DataSetInterface} and rules are parsed from the attributes.
132+
*
133+
* Please refer to the guide for more examples.
134+
*
135+
* Rules and data provided via separate methods have the highest priority over attributes and properties so, when using
136+
* together, the latter ones will be ignored without exception.
137+
*
138+
* When {@see RuleInterface} / {@see DataSetInterface} are not implemented, uses {@see ObjectParser} and supports
139+
* caching for data and attribute methods (partially) and rules (completely) which can be disabled on demand.
140+
*
141+
* For getting only rules by a class string, use {@see AttributesRulesProvider} instead.
17142
*
18143
* @link https://www.php.net/manual/en/language.attributes.overview.php
19144
*/
20145
final class ObjectDataSet implements RulesProviderInterface, DataSetInterface, AttributeTranslatorProviderInterface
21146
{
147+
/**
148+
* @var bool Whether an {@see $object} provided a data set by implementing {@see DataSetInterface}.
149+
*/
22150
private bool $dataSetProvided;
151+
/**
152+
* @var bool Whether an {@see $object} provided rules by implementing {@see RulesProviderInterface}.
153+
*/
23154
private bool $rulesProvided;
155+
/**
156+
* @var ObjectParser An object parser instance used to parse rules and data from attributes if these were not
157+
* provided by implementing {@see RulesProviderInterface} and {@see DataSetInterface} accordingly.
158+
*/
24159
private ObjectParser $parser;
25160

161+
/**
162+
* @param int $propertyVisibility Visibility levels the properties with rules / data must have. For example: public
163+
* and protected only, this means that the rest (private ones) will be skipped. Defaults to all visibility levels
164+
* (public, protected and private).
165+
* @param bool $useCache Whether to use cache for data and attribute methods (partially) and rules (completely).
166+
*/
26167
public function __construct(
168+
/**
169+
* @var object An object containing rules and data.
170+
*/
27171
private object $object,
28172
int $propertyVisibility = ReflectionProperty::IS_PRIVATE |
29173
ReflectionProperty::IS_PROTECTED |
30174
ReflectionProperty::IS_PUBLIC,
31-
bool $useCache = true
175+
bool $useCache = true,
32176
) {
33177
$this->dataSetProvided = $this->object instanceof DataSetInterface;
34178
$this->rulesProvided = $this->object instanceof RulesProviderInterface;
@@ -39,6 +183,21 @@ public function __construct(
39183
);
40184
}
41185

186+
/**
187+
* Returns {@see $object} rules specified via {@see RulesProviderInterface::getRules()} implementation or parsed
188+
* from attributes attached to class properties and class itself. For the latter case repetitive calls utilize cache
189+
* if it's enabled in {@see $useCache}. Rules provided via separate method have the highest priority over
190+
* attributes, so, when using together, the latter ones will be ignored without exception.
191+
*
192+
* @return iterable The resulting rules is an array with the following structure:
193+
*
194+
* ```php
195+
* [
196+
* [new AtLeast(['name', 'author'])], // Rules not bound to a specific attribute.
197+
* 'files' => [new Count(max: 3)], // Attribute specific rules.
198+
* ],
199+
* ```
200+
*/
42201
public function getRules(): iterable
43202
{
44203
if ($this->rulesProvided) {
@@ -48,15 +207,25 @@ public function getRules(): iterable
48207
return $object->getRules();
49208
}
50209

51-
// Providing data set assumes object has its own attributes and rules getting logic. So further parsing of
52-
// Reflection properties and rules is skipped intentionally.
210+
// Providing data set assumes object has its own rules getting logic. So further parsing of rules is skipped
211+
// intentionally.
53212
if ($this->dataSetProvided) {
54213
return [];
55214
}
56215

57216
return $this->parser->getRules();
58217
}
59218

219+
/**
220+
* Returns an attribute value by its name.
221+
*
222+
* Note that in case of non-existing attribute a default `null` value is returned. If you need to check the presence
223+
* of attribute or return a different default value, use {@see hasAttribute()} instead.
224+
*
225+
* @param string $attribute Attribute name.
226+
*
227+
* @return mixed Attribute value.
228+
*/
60229
public function getAttributeValue(string $attribute): mixed
61230
{
62231
if ($this->dataSetProvided) {
@@ -68,6 +237,14 @@ public function getAttributeValue(string $attribute): mixed
68237
return $this->parser->getAttributeValue($attribute);
69238
}
70239

240+
/**
241+
* Whether this data set has the attribute with a given name. Note that this means existence only and attributes
242+
* with empty values are treated as present too.
243+
*
244+
* @param string $attribute Attribute name.
245+
*
246+
* @return bool Whether the attribute exists: `true` - exists and `false` - otherwise.
247+
*/
71248
public function hasAttribute(string $attribute): bool
72249
{
73250
if ($this->dataSetProvided) {
@@ -79,6 +256,12 @@ public function hasAttribute(string $attribute): bool
79256
return $this->parser->hasAttribute($attribute);
80257
}
81258

259+
/**
260+
* Returns the validated data as a whole.
261+
*
262+
* @return mixed Validated data, has mixed type if it was provided via {@see DataSetInterface::getData()}
263+
* implementation, otherwise it's always an associative array - a mapping between property names and their values.
264+
*/
82265
public function getData(): mixed
83266
{
84267
if ($this->dataSetProvided) {
@@ -90,6 +273,12 @@ public function getData(): mixed
90273
return $this->parser->getData();
91274
}
92275

276+
/**
277+
* An optional attribute names translator. It's taken from the {@see $object} when
278+
* {@see AttributeTranslatorProviderInterface} is implemented. In case of it's missing, a `null` value is returned.
279+
*
280+
* @return AttributeTranslatorInterface|null An attribute translator instance or `null if it was not provided.
281+
*/
93282
public function getAttributeTranslator(): ?AttributeTranslatorInterface
94283
{
95284
return $this->parser->getAttributeTranslator();

src/DataSet/SingleValueDataSet.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
* $dataSet = new SingleValueDataSet('text');
1818
* $dataSet = new SingleValueDataSet(null);
1919
* $dataSet = new SingleValueDataSet(false);
20+
* $dataSet = new SingleValueDataSet([]);
21+
* $dataSet = new SingleValueDataSet(new \stdClass());
2022
* ```
2123
*
2224
* When using validator, there is no need to wrap data manually. Such types be automatically wrapped with
2325
* {@see SingleValueDataSet} by {@see DataSetNormalizer} during validation.
2426
*
25-
* For arrays and objects use {@see ArrayDataSet} and {@see ObjectDataSet} accordingly.
27+
* For arrays and objects {@see ArrayDataSet} and {@see ObjectDataSet} can be used accordingly.
2628
*/
2729
final class SingleValueDataSet implements DataSetInterface
2830
{
@@ -49,7 +51,7 @@ public function getAttributeValue(string $attribute): mixed
4951

5052
/**
5153
* A getter for {@see $data} property. Returns the validated data as a whole. In this case the single value itself
52-
* returned as a data because it can not be decoupled.
54+
* is returned as a data because it can not be decoupled.
5355
*
5456
* @return mixed Single value of any (mixed) data type.
5557
*/

0 commit comments

Comments
 (0)