Test Failed
Pull Request — master (#323)
by
unknown
03:15
created

ObjectDataSet::getReflectionProperties()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 18
ccs 11
cts 11
cp 1
rs 9.9666
cc 4
nc 4
nop 0
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\DataSet;
6
7
use JetBrains\PhpStorm\ArrayShape;
8
use JetBrains\PhpStorm\ExpectedValues;
9
use ReflectionAttribute;
10
use ReflectionObject;
11
use ReflectionProperty;
12
use Yiisoft\Validator\AttributeEventInterface;
13
use Yiisoft\Validator\DataSetInterface;
14
use Yiisoft\Validator\RuleInterface;
15
use Yiisoft\Validator\RulesProviderInterface;
16
17
use function array_key_exists;
18
19
/**
20
 * This data set makes use of attributes introduced in PHP 8. It simplifies rules configuration process, especially for
21
 * nested data and relations. Please refer to the guide for examples.
22
 *
23
 * @link https://www.php.net/manual/en/language.attributes.overview.php
24
 */
25
final class ObjectDataSet implements RulesProviderInterface, DataSetInterface
26
{
27
    private bool $dataSetProvided;
28
    private bool $rulesProvided;
29
30
    #[ArrayShape([
31
        [
32
            'rules' => 'iterable',
33
            'reflectionAttributes' => 'array',
34 53
        ],
35
    ])]
36
    private static array $cache = [];
37
    private string $cacheKey;
38 53
39
    public function __construct(
40
        private object $object,
41 4
        private int $propertyVisibility = ReflectionProperty::IS_PRIVATE |
42
        ReflectionProperty::IS_PROTECTED |
43 4
        ReflectionProperty::IS_PUBLIC
44
    ) {
45
        $this->dataSetProvided = $this->object instanceof DataSetInterface;
46 36
        $this->rulesProvided = $this->object instanceof RulesProviderInterface;
47
        $this->cacheKey = $this->object::class . '_' . $this->propertyVisibility;
48 36
    }
49
50
    public function getRules(): iterable
51 31
    {
52
        // Providing data set assumes object has its own attributes and rules getting logic. So further parsing of
53 31
        // Reflection properties and rules is skipped intentionally at the very beginning.
54 2
        if ($this->dataSetProvided) {
55
            return [];
56
        }
57 29
58 29
        if ($this->rulesProvided) {
59 29
            return $this->object->getRules();
60
        }
61
62 29
        if ($this->hasCache()) {
63
            return $this->getCacheItem('rules');
64 29
        }
65
66 29
        $rules = [];
67
        foreach ($this->getReflectionProperties() as $property) {
68
            // TODO: use Generator to collect attributes.
69 15
            $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
70
            foreach ($attributes as $attribute) {
71 15
                $rule = $attribute->newInstance();
72 2
                $rules[$property->getName()][] = $rule;
73
74
                if ($rule instanceof AttributeEventInterface) {
75 13
                    $rule->afterInitAttribute($this);
76 13
                }
77 10
            }
78
        }
79 13
80
        $this->setCacheItem('rules', $rules);
81
82
        return $rules;
83 53
    }
84
85 53
    private function getReflectionProperties(): array
86 53
    {
87
        if ($this->hasCache()) {
88 53
            return $this->getCacheItem('reflectionProperties');
89
        }
90
91 53
        $reflection = new ReflectionObject($this->object);
92 2
        foreach ($reflection->getProperties($this->propertyVisibility) as $property) {
93
            if (PHP_VERSION_ID < 80100) {
94
                $property->setAccessible(true);
95 51
            }
96 51
97 47
            $reflectionProperties[$property->getName()] = $property;
98 47
        }
99
100 47
        $this->setCacheItem('reflectionProperties', $reflectionProperties);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $reflectionProperties seems to be defined by a foreach iteration on line 92. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
101
102 47
        return $reflectionProperties;
103 6
    }
104
105
    public function getObject(): object
106 42
    {
107 42
        return $this->object;
108 32
    }
109 32
110
    public function getAttributeValue(string $attribute): mixed
111 32
    {
112 4
        if ($this->dataSetProvided) {
113
            return $this->object->getAttributeValue($attribute);
114
        }
115
116
        return $this->getData()[$attribute] ?? null;
117
    }
118
119
    public function hasAttribute(string $attribute): bool
120
    {
121
        return $this->dataSetProvided
122
            ? $this->object->hasAttribute($attribute)
123
            : array_key_exists($attribute, $this->getReflectionProperties());
124
    }
125
126
    public function getData(): array
127
    {
128
        if ($this->dataSetProvided) {
129
            return $this->object->getData();
130
        }
131
132
        $data = [];
133
        foreach ($this->getReflectionProperties() as $name => $property) {
134
            $data[$name] = $property->getValue($this->object);
135
        }
136
137
        return $data;
138
    }
139
140
    private function hasCache(): bool
141
    {
142
        return array_key_exists($this->cacheKey, self::$cache);
143
    }
144
145
    private function getCacheItem(#[ExpectedValues(['rules', 'reflectionAttributes'])] string $name): array
146
    {
147
        return self::$cache[$this->cacheKey][$name];
148
    }
149
150
    private function setCacheItem(#[ExpectedValues(['rules', 'reflectionAttributes'])] string $name, array $rules): void
151
    {
152
        self::$cache[$this->cacheKey][$name] = $rules;
153
    }
154
}
155