Passed
Pull Request — master (#323)
by
unknown
02:49
created

ObjectDataSet::getData()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 6
c 3
b 0
f 0
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 10
cc 3
nc 3
nop 0
crap 3
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
        ],
35
    ])]
36
    private static array $cache = [];
37
    private string|null $cacheKey = null;
38
39 47
    public function __construct(
40
        private object $object,
41
        private int $propertyVisibility = ReflectionProperty::IS_PRIVATE |
42
        ReflectionProperty::IS_PROTECTED |
43
        ReflectionProperty::IS_PUBLIC,
44
        private bool $useCache = true
45
    ) {
46 47
        $this->dataSetProvided = $this->object instanceof DataSetInterface;
47 47
        $this->rulesProvided = $this->object instanceof RulesProviderInterface;
48
49 47
        if ($this->canCache()) {
50 47
            $this->cacheKey = $this->object::class . '_' . $this->propertyVisibility;
51
        }
52
    }
53
54 53
    public function getRules(): iterable
55
    {
56 53
        if ($this->rulesProvided) {
57 13
            return $this->object->getRules();
58
        }
59
60
        // Providing data set assumes object has its own attributes and rules getting logic. So further parsing of
61
        // Reflection properties and rules is skipped intentionally.
62 40
        if ($this->dataSetProvided) {
63 5
            return [];
64
        }
65
66 35
        if ($this->hasCacheItem('rules')) {
67 7
            return $this->getCacheItem('rules');
68
        }
69
70 32
        $rules = [];
71 32
        foreach ($this->getReflectionProperties() as $property) {
72
            // TODO: use Generator to collect attributes.
73 31
            $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
74 31
            foreach ($attributes as $attribute) {
75 29
                $rule = $attribute->newInstance();
76 29
                $rules[$property->getName()][] = $rule;
77
78 29
                if ($rule instanceof AttributeEventInterface) {
79 3
                    $rule->afterInitAttribute($this);
80
                }
81
            }
82
        }
83
84 31
        if ($this->canCache()) {
85 31
            $this->setCacheItem('rules', $rules);
86
        }
87
88 31
        return $rules;
89
    }
90
91 3
    public function getObject(): object
92
    {
93 3
        return $this->object;
94
    }
95
96 45
    public function getAttributeValue(string $attribute): mixed
97
    {
98 45
        if ($this->dataSetProvided) {
99 8
            return $this->object->getAttributeValue($attribute);
100
        }
101
102 37
        return $this->getData()[$attribute] ?? null;
103
    }
104
105 27
    public function hasAttribute(string $attribute): bool
106
    {
107 27
        return $this->dataSetProvided
108
            ? $this->object->hasAttribute($attribute)
109 27
            : array_key_exists($attribute, $this->getReflectionProperties());
110
    }
111
112 58
    public function getData(): array
113
    {
114 58
        if ($this->dataSetProvided) {
115 12
            return $this->object->getData();
116
        }
117
118 46
        $data = [];
119 46
        foreach ($this->getReflectionProperties() as $name => $property) {
120 43
            $data[$name] = $property->getValue($this->object);
121
        }
122
123 46
        return $data;
124
    }
125
126 56
    private function getReflectionProperties(): array
127
    {
128 56
        if ($this->hasCacheItem('reflectionProperties')) {
129 39
            return $this->getCacheItem('reflectionProperties');
130
        }
131
132 45
        $reflection = new ReflectionObject($this->object);
133 45
        $reflectionProperties = [];
134
135 45
        foreach ($reflection->getProperties($this->propertyVisibility) as $property) {
136 43
            if (PHP_VERSION_ID < 80100) {
137 43
                $property->setAccessible(true);
138
            }
139
140 43
            $reflectionProperties[$property->getName()] = $property;
141
        }
142
143 45
        if ($this->canCache()) {
144 44
            $this->setCacheItem('reflectionProperties', $reflectionProperties);
145
        }
146
147 45
        return $reflectionProperties;
148
    }
149
150 51
    private function canCache(): bool
151
    {
152 51
        return $this->useCache === true;
153
    }
154
155 56
    private function hasCacheItem(#[ExpectedValues(['rules', 'reflectionProperties'])] string $name): bool
156
    {
157 56
        if (!array_key_exists($this->cacheKey, self::$cache)) {
158 45
            return false;
159
        }
160
161 39
        return array_key_exists($name, self::$cache[$this->cacheKey]);
162
    }
163
164 39
    private function getCacheItem(#[ExpectedValues(['rules', 'reflectionProperties'])] string $name): array
165
    {
166 39
        return self::$cache[$this->cacheKey][$name];
167
    }
168
169 45
    private function setCacheItem(#[ExpectedValues(['rules', 'reflectionProperties'])] string $name, array $rules): void
170
    {
171 45
        self::$cache[$this->cacheKey][$name] = $rules;
172
    }
173
}
174