Skip to content

Commit 1423457

Browse files
authored
1 parent 14d3e85 commit 1423457

20 files changed

Lines changed: 657 additions & 134 deletions

.github/workflows/build.yml

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,10 @@ name: build
2323

2424
jobs:
2525
phpunit:
26-
uses: &phpunit-uses yiisoft/actions/.github/workflows/phpunit.yml@master
26+
uses: yiisoft/actions/.github/workflows/phpunit.yml@master
2727
with:
2828
extensions: intl
29-
os: &phpunit-os >-
29+
os: >-
3030
['ubuntu-latest', 'windows-latest']
31-
php: &phpunit-php >-
31+
php: >-
3232
['8.0', '8.1']
33-
phpunit_without_extensions:
34-
uses: *phpunit-uses
35-
with:
36-
os: *phpunit-os
37-
php: *phpunit-php

README.md

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -439,33 +439,16 @@ Here are some technical details:
439439
referenced class rules as it owns.
440440
- In case of a flat array `Point::$rgb`, a property type `array` needs to be declared.
441441

442-
Pass the base DTO to `AttributeDataSet` and use it for validation.
442+
Pass object directly to `validate()` method:
443443

444444
```php
445-
use Yiisoft\Validator\DataSet\AttributeDataSet;
446445
use Yiisoft\Validator\ValidatorInterface;
447446

448447
// Usually obtained from container
449448
$validator = $container->get(ValidatorInterface::class);
450449

451-
$data = [
452-
// ...
453-
];
454-
$dataSet = new AttributeDataSet(new ChartsData(), $data);
455-
$errors = $validator->validate($dataSet)->getErrorMessagesIndexedByPath();
456-
```
457-
458-
If the object implements `DataSetInterface` just pass it directly:
459-
460-
```php
461-
use Yiisoft\Validator\DataSet\AttributeDataSet;
462-
use Yiisoft\Validator\ValidatorInterface;
463-
464-
// Usually obtained from container
465-
$validator = $container->get(ValidatorInterface::class);
466-
467-
$dataSet = new ChartsData();
468-
$errors = $validator->validate($dataSet)->getErrorMessagesIndexedByPath();
450+
$coordinates = new Coordinates();
451+
$errors = $validator->validate($coordinates)->getErrorMessagesIndexedByPath();
469452
```
470453

471454
##### Traits

src/DataSet/ArrayDataSet.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@
88

99
final class ArrayDataSet implements DataSetInterface
1010
{
11-
use ArrayDataTrait;
11+
private array $data;
1212

1313
public function __construct(array $data = [])
1414
{
1515
$this->data = $data;
1616
}
17+
18+
public function getAttributeValue(string $attribute): mixed
19+
{
20+
return $this->data[$attribute] ?? null;
21+
}
22+
23+
public function getData(): array
24+
{
25+
return $this->data;
26+
}
1727
}

src/DataSet/ArrayDataTrait.php

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/DataSet/AttributeDataSet.php

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@
66

77
use Yiisoft\Validator\DataSetInterface;
88

9-
final class ScalarDataSet implements DataSetInterface
9+
final class MixedDataSet implements DataSetInterface
1010
{
11-
private $value;
11+
private mixed $value;
1212

13-
public function __construct($value)
13+
public function __construct(mixed $value)
1414
{
1515
$this->value = $value;
1616
}
1717

1818
public function getAttributeValue(string $attribute): mixed
1919
{
20-
return $this->value;
20+
return null;
2121
}
2222

2323
public function getData(): mixed

src/DataSet/ObjectDataSet.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Validator\DataSet;
6+
7+
use ReflectionAttribute;
8+
use ReflectionObject;
9+
use ReflectionProperty;
10+
use Yiisoft\Validator\DataSetInterface;
11+
use Yiisoft\Validator\RuleInterface;
12+
use Yiisoft\Validator\RulesProviderInterface;
13+
14+
/**
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 example.
17+
*
18+
* @link https://www.php.net/manual/en/language.attributes.overview.php
19+
*/
20+
final class ObjectDataSet implements RulesProviderInterface, DataSetInterface
21+
{
22+
private object $object;
23+
24+
private bool $dataSetProvided;
25+
26+
private int $propertyVisibility;
27+
28+
/**
29+
* @var ReflectionProperty[]
30+
*/
31+
private array $reflectionProperties = [];
32+
33+
private iterable $rules;
34+
35+
public function __construct(
36+
object $object,
37+
int $propertyVisibility = ReflectionProperty::IS_PRIVATE|ReflectionProperty::IS_PROTECTED|ReflectionProperty::IS_PUBLIC
38+
) {
39+
$this->object = $object;
40+
$this->propertyVisibility = $propertyVisibility;
41+
$this->parseObject();
42+
}
43+
44+
public function getRules(): iterable
45+
{
46+
return $this->rules;
47+
}
48+
49+
public function getAttributeValue(string $attribute): mixed
50+
{
51+
if ($this->dataSetProvided) {
52+
return $this->object->getAttributeValue($attribute);
53+
}
54+
55+
return isset($this->reflectionProperties[$attribute])
56+
? $this->reflectionProperties[$attribute]->getValue($this->object)
57+
: null;
58+
}
59+
60+
public function getData(): array
61+
{
62+
if ($this->dataSetProvided) {
63+
return $this->object->getData();
64+
}
65+
66+
$data = [];
67+
foreach ($this->reflectionProperties as $name => $property) {
68+
$data[$name] = $property->getValue($this->object);
69+
}
70+
return $data;
71+
}
72+
73+
// TODO: use Generator to collect attributes
74+
private function parseObject(): void
75+
{
76+
$objectProvidedRules = $this->object instanceof RulesProviderInterface;
77+
$this->dataSetProvided = $this->object instanceof DataSetInterface;
78+
79+
$this->rules = $objectProvidedRules ? $this->object->getRules() : [];
80+
81+
if ($this->dataSetProvided) {
82+
return;
83+
}
84+
85+
$reflection = new ReflectionObject($this->object);
86+
foreach ($reflection->getProperties() as $property) {
87+
if (!$this->isUseProperty($property)) {
88+
continue;
89+
}
90+
91+
if (PHP_VERSION_ID < 80100) {
92+
$property->setAccessible(true);
93+
}
94+
$this->reflectionProperties[$property->getName()] = $property;
95+
96+
if (!$objectProvidedRules) {
97+
$attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
98+
foreach ($attributes as $attribute) {
99+
$this->rules[$property->getName()][] = $attribute->newInstance();
100+
}
101+
}
102+
}
103+
}
104+
105+
private function isUseProperty(ReflectionProperty $property): bool
106+
{
107+
return ($property->isPublic() && ($this->propertyVisibility&ReflectionProperty::IS_PUBLIC))
108+
|| ($property->isPrivate() && ($this->propertyVisibility&ReflectionProperty::IS_PRIVATE))
109+
|| ($property->isProtected() && ($this->propertyVisibility&ReflectionProperty::IS_PROTECTED));
110+
}
111+
}

src/Validator.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace Yiisoft\Validator;
66

7+
use Closure;
78
use InvalidArgumentException;
89
use JetBrains\PhpStorm\Pure;
910
use Psr\Container\ContainerExceptionInterface;
1011
use Psr\Container\NotFoundExceptionInterface;
1112
use Yiisoft\Validator\DataSet\ArrayDataSet;
12-
use Yiisoft\Validator\DataSet\AttributeDataSet;
13-
use Yiisoft\Validator\DataSet\ScalarDataSet;
13+
use Yiisoft\Validator\DataSet\ObjectDataSet;
14+
use Yiisoft\Validator\DataSet\MixedDataSet;
1415
use Yiisoft\Validator\Rule\Callback;
1516
use Yiisoft\Validator\Rule\Trait\PreValidateTrait;
1617

@@ -30,11 +31,11 @@ public function __construct(private RuleHandlerResolverInterface $ruleHandlerRes
3031

3132
/**
3233
* @param DataSetInterface|mixed|RulesProviderInterface $data
33-
* @param iterable<\Closure|\Closure[]|RuleInterface|RuleInterface[]>|null $rules
34+
* @param iterable<Closure|Closure[]|RuleInterface|RuleInterface[]>|null $rules
3435
*/
3536
public function validate(mixed $data, ?iterable $rules = null): Result
3637
{
37-
$data = $this->normalizeDataSet($data, $rules !== null);
38+
$data = $this->normalizeDataSet($data);
3839
if ($rules === null && $data instanceof RulesProviderInterface) {
3940
$rules = $data->getRules();
4041
}
@@ -79,22 +80,26 @@ public function validate(mixed $data, ?iterable $rules = null): Result
7980
}
8081

8182
#[Pure]
82-
private function normalizeDataSet($data, bool $hasRules): DataSetInterface
83+
private function normalizeDataSet($data): DataSetInterface
8384
{
8485
if ($data instanceof DataSetInterface) {
85-
return $hasRules ? new AttributeDataSet($data) : $data;
86+
return $data;
8687
}
8788

88-
if (is_object($data) || is_array($data)) {
89-
return new ArrayDataSet((array)$data);
89+
if (is_object($data)) {
90+
return new ObjectDataSet($data);
9091
}
9192

92-
return new ScalarDataSet($data);
93+
if (is_array($data)) {
94+
return new ArrayDataSet($data);
95+
}
96+
97+
return new MixedDataSet($data);
9398
}
9499

95100
/**
96101
* @param $value
97-
* @param iterable<\Closure|\Closure[]|RuleInterface|RuleInterface[]> $rules
102+
* @param iterable<Closure|Closure[]|RuleInterface|RuleInterface[]> $rules
98103
* @param ValidationContext $context
99104
*
100105
* @throws ContainerExceptionInterface

tests/DataSet/ArrayDataSetTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Validator\Tests\DataSet;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Yiisoft\Validator\DataSet\ArrayDataSet;
9+
10+
final class ArrayDataSetTest extends TestCase
11+
{
12+
public function testBase(): void
13+
{
14+
$data = new ArrayDataSet(['test' => 'hello']);
15+
16+
$this->assertNull($data->getAttributeValue('non-exist'));
17+
$this->assertSame('hello', $data->getAttributeValue('test'));
18+
$this->assertSame(['test' => 'hello'], $data->getData());
19+
}
20+
}

tests/DataSet/MixedDataSetTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Validator\Tests\DataSet;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Yiisoft\Validator\DataSet\MixedDataSet;
9+
10+
final class MixedDataSetTest extends TestCase
11+
{
12+
public function testBase(): void
13+
{
14+
$data = new MixedDataSet(['test' => 'hello']);
15+
16+
$this->assertNull($data->getAttributeValue('test'));
17+
$this->assertSame(['test' => 'hello'], $data->getData());
18+
}
19+
}

0 commit comments

Comments
 (0)