Passed
Push — master ( 95f319...f01b66 )
by Alexander
02:23
created

AttributeDataSet::getRules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\DataSet;
6
7
use InvalidArgumentException;
8
use ReflectionClass;
9
use ReflectionException;
10
use Yiisoft\Validator\Attribute\HasMany;
11
use Yiisoft\Validator\Attribute\HasOne;
12
use Yiisoft\Validator\DataSetInterface;
13
use Yiisoft\Validator\Rule\Each;
14
use Yiisoft\Validator\Rule\Nested;
15
use Yiisoft\Validator\RuleInterface;
16
use Yiisoft\Validator\RulesProviderInterface;
17
18
/**
19
 * This data set makes use of attributes introduced in PHP 8. It simplifies rules configuration process, especially for
20
 * nested data and relations. Please refer to the guide for example.
21
 *
22
 * @link https://www.php.net/manual/en/language.attributes.overview.php
23
 */
24
final class AttributeDataSet implements RulesProviderInterface, DataSetInterface
25
{
26
    use ArrayDataTrait;
27
28
    private object $baseAnnotatedObject;
29
30 2
    public function __construct(object $baseAnnotatedObject, array $data = [])
31
    {
32 2
        $this->baseAnnotatedObject = $baseAnnotatedObject;
33 2
        $this->data = $data;
34
    }
35
36 2
    public function getRules(): iterable
37
    {
38 2
        $classMeta = new ReflectionClass($this->baseAnnotatedObject);
39
40 2
        return $this->handleAttributes($classMeta);
41
    }
42
43 2
    private function handleAttributes(ReflectionClass $classMeta): array
44
    {
45 2
        $rules = [];
46 2
        foreach ($classMeta->getProperties() as $property) {
47 2
            if ($property->isStatic()) {
48
                continue;
49
            }
50
51 2
            foreach ([HasMany::class, HasOne::class] as $className) {
52 2
                $attributes = $property->getAttributes($className);
53 2
                if (empty($attributes)) {
54 2
                    continue;
55
                }
56
57 1
                $relatedClassName = $attributes[0]->getArguments()[0];
58
59
                try {
60 1
                    $relatedClassMeta = new ReflectionClass($relatedClassName);
61
                } catch (ReflectionException) {
62
                    throw new InvalidArgumentException("Class \"$relatedClassName\" not found.");
63
                }
64
65 1
                $nestedRule = new Nested(
66 1
                    $this->handleAttributes($relatedClassMeta),
67 1
                    ...(($property->getAttributes(Nested::class)[0] ?? null)?->getArguments() ?? [])
0 ignored issues
show
Bug introduced by
$property->getAttributes...tArguments() ?? array() is expanded, but the parameter $errorWhenPropertyPathIsNotFound of Yiisoft\Validator\Rule\Nested::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

67
                    /** @scrutinizer ignore-type */ ...(($property->getAttributes(Nested::class)[0] ?? null)?->getArguments() ?? [])
Loading history...
68
                );
69
70 1
                if ($className !== HasMany::class) {
71 1
                    $rules[$property->getName()] = $nestedRule;
72
                } else {
73
                    /** @psalm-suppress UndefinedMethod */
74 1
                    $rules[$property->getName()][] = new Each(
75 1
                        [$nestedRule],
76 1
                        ...(($property->getAttributes(Each::class)[0] ?? null)?->getArguments() ?? [])
0 ignored issues
show
Bug introduced by
$property->getAttributes...tArguments() ?? array() is expanded, but the parameter $incorrectInputMessage of Yiisoft\Validator\Rule\Each::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

76
                        /** @scrutinizer ignore-type */ ...(($property->getAttributes(Each::class)[0] ?? null)?->getArguments() ?? [])
Loading history...
77
                    );
78
                }
79
            }
80
81 2
            $eachRuleFound = false;
82 2
            $eachRules = [];
83 2
            $attributes = $property->getAttributes();
84 2
            foreach ($attributes as $attribute) {
85 2
                if (!is_subclass_of($attribute->getName(), RuleInterface::class)) {
86 1
                    continue;
87
                }
88
89 2
                if ($attribute->getName() === Each::class) {
90 1
                    $eachRuleFound = true;
91
92 1
                    continue;
93
                }
94
95 2
                if ($attribute->getName() === Nested::class) {
96 1
                    continue;
97
                }
98
99
                /** @psalm-suppress UndefinedMethod */
100 2
                $eachRuleFound
101 1
                    ? $eachRules[] = $attribute->newInstance()
102 2
                    : $rules[$property->getName()][] = $attribute->newInstance();
103
            }
104
105 2
            if (!$eachRules || (string) $property->getType() !== 'array') {
106 2
                continue;
107
            }
108
109
            /**
110
             * @psalm-suppress UndefinedMethod, InvalidArgument
111
             */
112 1
            $rules[$property->getName()][] = new Each(
113
                $eachRules,
114 1
                ...(($property->getAttributes(Each::class)[0] ?? null)?->getArguments() ?? [])
115
            );
116
        }
117
118 2
        return $rules;
119
    }
120
}
121