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

ObjectDataSet::getCacheItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 8
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\DataSet;
6
7
use JetBrains\PhpStorm\ArrayShape;
8
use ReflectionAttribute;
9
use ReflectionObject;
10
use ReflectionProperty;
11
use Yiisoft\Validator\AttributeEventInterface;
12
use Yiisoft\Validator\DataSetInterface;
13
use Yiisoft\Validator\RuleInterface;
14
use Yiisoft\Validator\RulesProviderInterface;
15
16
use function get_class;
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 examples.
21
 *
22
 * @link https://www.php.net/manual/en/language.attributes.overview.php
23
 */
24
final class ObjectDataSet implements RulesProviderInterface, DataSetInterface
25
{
26
    private bool $dataSetProvided;
27
28
    /**
29
     * @var ReflectionProperty[] Used to avoid error "Typed property must not be accessed before initialization".
30
     */
31
    private array $reflectionProperties = [];
32
    private string $cacheKey;
33
34
    #[ArrayShape([
35
        [
36
            'rules' => 'iterable',
37
            'reflectionProperties' => 'array',
38
            'data' => 'array',
39
        ],
40
    ])]
41
    private static array $cache;
42
43
    private iterable $rules;
44
45 48
    public function __construct(
46
        private object $object,
47
        private int $propertyVisibility = ReflectionProperty::IS_PRIVATE |
48
        ReflectionProperty::IS_PROTECTED |
49
        ReflectionProperty::IS_PUBLIC,
50
    ) {
51 48
        $this->cacheKey = get_class($this->object);
52 48
        $this->parseObject();
53
    }
54
55 4
    public function getObject(): object
56
    {
57 4
        return $this->object;
58
    }
59
60 40
    public function getRules(): iterable
61
    {
62 40
        return $this->rules;
63
    }
64
65 32
    public function getAttributeValue(string $attribute): mixed
66
    {
67 32
        if ($this->dataSetProvided) {
68 4
            return $this->object->getAttributeValue($attribute);
69
        }
70
71 28
        return $this->getData()[$attribute] ?? null;
72
    }
73
74 34
    public function getData(): array
75
    {
76 34
        $cacheItem = $this->getCacheItem();
77 34
        if (isset($cacheItem['data'])) {
78 16
            return $cacheItem['data'];
79
        }
80
81 26
        if ($this->dataSetProvided) {
82 3
            $data = $this->object->getData();
83
        } else {
84 23
            $data = [];
85 23
            foreach ($this->reflectionProperties as $name => $property) {
86 23
                $data[$name] = $property->getValue($this->object);
87
            }
88
        }
89
90 26
        $this->updateCacheItem('data', $data);
91
92 26
        return $data;
93
    }
94
95
    // TODO: Use Generator to collect attributes
96 48
    private function parseObject(): void
97
    {
98 48
        $this->dataSetProvided = $this->object instanceof DataSetInterface;
99
100
        if (
101 48
            $this->hasCacheItem() &&
102 48
            $this->propertyVisibility === $this->getCacheItem()['propertyVisibility']
103
        ) {
104 11
            $this->rules = $this->getCacheItem()['rules'];
105 11
            $this->reflectionProperties = $this->getCacheItem()['reflectionProperties'] ?? [];
106
107 11
            return;
108
        }
109
110 38
        $this->rules = $this->parseRules();
111
112 35
        $this->updateCacheItem('rules', $this->rules);
113 35
        $this->updateCacheItem('propertyVisibility', $this->propertyVisibility);
114 35
        $this->deleteCacheItem('data');
115
    }
116
117 38
    private function parseRules(): iterable
118
    {
119 38
        $objectHasRules = $this->object instanceof RulesProviderInterface;
120 38
        $rules = $objectHasRules ? $this->object->getRules() : [];
121
122 38
        if ($this->dataSetProvided) {
123 3
            return $rules;
124
        }
125
126 35
        $reflection = new ReflectionObject($this->object);
127 35
        $reflectionProperties = [];
128
129 35
        foreach ($reflection->getProperties($this->propertyVisibility) as $property) {
130 34
            if (PHP_VERSION_ID < 80100) {
131 34
                $property->setAccessible(true);
132
            }
133
134 34
            $reflectionProperties[$property->getName()] = $property;
135
136 34
            if ($objectHasRules === true) {
137 3
                continue;
138
            }
139
140 32
            $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
141 32
            foreach ($attributes as $attribute) {
142 29
                $rule = $attribute->newInstance();
143 29
                $rules[$property->getName()][] = $rule;
144
145 29
                if ($rule instanceof AttributeEventInterface) {
146 4
                    $rule->afterInitAttribute($this);
147
                }
148
            }
149
        }
150
151 32
        $this->reflectionProperties = $reflectionProperties;
152 32
        $this->updateCacheItem('reflectionProperties', $this->reflectionProperties);
153
154 32
        return $rules;
155
    }
156
157 48
    private function hasCacheItem(): bool
158
    {
159 48
        return isset(self::$cache[$this->cacheKey]);
160
    }
161
162 36
    #[ArrayShape([
163
        'rules' => 'iterable',
164
        'reflectionProperties' => 'array',
165
        'data' => 'array',
166
    ])]
167
    private function getCacheItem(): array
168
    {
169 36
        return self::$cache[$this->cacheKey];
170
    }
171
172 35
    private function updateCacheItem($key, $value): void
173
    {
174 35
        self::$cache[$this->cacheKey][$key] = $value;
175
    }
176
177 35
    private function deleteCacheItem(string $key): void
178
    {
179 35
        unset(self::$cache[$this->cacheKey][$key]);
180
    }
181
}
182