Passed
Push — master ( 8f2b33...1f21d2 )
by Alexander
02:58
created

AttributeDataSet   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 93
Duplicated Lines 0 %

Test Coverage

Coverage 93.48%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 49
c 1
b 0
f 0
dl 0
loc 93
ccs 43
cts 46
cp 0.9348
rs 10
wmc 16

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getRules() 0 5 1
C handleAttributes() 0 74 14
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\Rule\Each;
13
use Yiisoft\Validator\Rule\Nested;
14
use Yiisoft\Validator\RuleInterface;
15
use Yiisoft\Validator\RulesProviderInterface;
16
17
/**
18
 * This data set makes use of attributes introduced in PHP 8. It simplifies rules configuration process, especially for
19
 * nested data and relations. Please refer to the guide for example.
20
 *
21
 * @link https://www.php.net/manual/en/language.attributes.overview.php
22
 */
23
final class AttributeDataSet implements RulesProviderInterface
24
{
25
    use ArrayDataTrait;
26
27
    private object $baseAnnotatedObject;
28
29 2
    public function __construct(object $baseAnnotatedObject, array $data = [])
30
    {
31 2
        $this->baseAnnotatedObject = $baseAnnotatedObject;
32 2
        $this->data = $data;
33
    }
34
35 2
    public function getRules(): iterable
36
    {
37 2
        $classMeta = new ReflectionClass($this->baseAnnotatedObject);
38
39 2
        return $this->handleAttributes($classMeta);
40
    }
41
42 2
    private function handleAttributes(ReflectionClass $classMeta): array
43
    {
44 2
        $rules = [];
45 2
        foreach ($classMeta->getProperties() as $property) {
46 2
            if ($property->isStatic()) {
47
                continue;
48
            }
49
50 2
            foreach ([HasMany::class, HasOne::class] as $className) {
51 2
                $attributes = $property->getAttributes($className);
52 2
                if (empty($attributes)) {
53 2
                    continue;
54
                }
55
56 1
                $relatedClassName = $attributes[0]->getArguments()[0];
57
58
                try {
59 1
                    $relatedClassMeta = new ReflectionClass($relatedClassName);
60
                } catch (ReflectionException) {
61
                    throw new InvalidArgumentException("Class \"$relatedClassName\" not found.");
62
                }
63
64 1
                $nestedRule = new Nested(
65 1
                    $this->handleAttributes($relatedClassMeta),
66 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

66
                    /** @scrutinizer ignore-type */ ...(($property->getAttributes(Nested::class)[0] ?? null)?->getArguments() ?? [])
Loading history...
67
                );
68
69 1
                if ($className !== HasMany::class) {
70 1
                    $rules[$property->getName()] = $nestedRule;
71
                } else {
72
                    /** @psalm-suppress UndefinedMethod */
73 1
                    $rules[$property->getName()][] = new Each(
74 1
                        [$nestedRule],
75 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

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