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

ObjectDataSet::getCacheItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
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 stdClass;
13
use Yiisoft\Validator\AttributeEventInterface;
14
use Yiisoft\Validator\DataSetInterface;
15
use Yiisoft\Validator\RuleInterface;
16
use Yiisoft\Validator\RulesProviderInterface;
17
18
use function array_key_exists;
19
20
/**
21
 * This data set makes use of attributes introduced in PHP 8. It simplifies rules configuration process, especially for
22
 * nested data and relations. Please refer to the guide for examples.
23
 *
24
 * @link https://www.php.net/manual/en/language.attributes.overview.php
25
 */
26
final class ObjectDataSet implements RulesProviderInterface, DataSetInterface
27
{
28
    private bool $dataSetProvided;
29
    private bool $rulesProvided;
30
31
    #[ArrayShape([
32
        [
33
            'rules' => 'iterable',
34 52
            'reflectionAttributes' => 'array',
35
        ],
36
    ])]
37
    private static array $cache = [];
38 52
    private string|null $cacheKey = null;
39
40
    public function __construct(
41 3
        private object $object,
42
        private int $propertyVisibility = ReflectionProperty::IS_PRIVATE |
43 3
        ReflectionProperty::IS_PROTECTED |
44
        ReflectionProperty::IS_PUBLIC
45
    ) {
46 37
        $this->dataSetProvided = $this->object instanceof DataSetInterface;
47
        $this->rulesProvided = $this->object instanceof RulesProviderInterface;
48 37
49
        if ($this->canCache()) {
50
            $this->cacheKey = $this->object::class . '_' . $this->propertyVisibility;
51 32
        }
52
    }
53 32
54 2
    public function getRules(): iterable
55
    {
56
        if ($this->rulesProvided) {
57 30
            return $this->object->getRules();
58 30
        }
59 30
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 30
        if ($this->dataSetProvided) {
63
            return [];
64 30
        }
65
66 30
        if ($this->hasCacheItem('rules')) {
67
            return $this->getCacheItem('rules');
68
        }
69 15
70
        $rules = [];
71 15
        foreach ($this->getReflectionProperties() as $property) {
72 2
            // TODO: use Generator to collect attributes.
73
            $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
74
            foreach ($attributes as $attribute) {
75 13
                $rule = $attribute->newInstance();
76 13
                $rules[$property->getName()][] = $rule;
77 10
78
                if ($rule instanceof AttributeEventInterface) {
79 13
                    $rule->afterInitAttribute($this);
80
                }
81
            }
82
        }
83 52
84
        if ($this->canCache()) {
85 52
            $this->setCacheItem('rules', $rules);
86 52
        }
87
88 52
        return $rules;
89
    }
90
91 52
    private function getReflectionProperties(): array
92 2
    {
93
        if ($this->hasCacheItem('reflectionProperties')) {
94
            return $this->getCacheItem('reflectionProperties');
95 50
        }
96 50
97 46
        $reflection = new ReflectionObject($this->object);
98 46
        $reflectionProperties = [];
99
100 46
        foreach ($reflection->getProperties($this->propertyVisibility) as $property) {
101
            if (PHP_VERSION_ID < 80100) {
102 46
                $property->setAccessible(true);
103 6
            }
104
105
            $reflectionProperties[$property->getName()] = $property;
106 41
        }
107 41
108 31
        if ($this->canCache()) {
109 31
            $this->setCacheItem('reflectionProperties', $reflectionProperties);
110
        }
111 31
112 3
        return $reflectionProperties;
113
    }
114
115
    public function getObject(): object
116
    {
117
        return $this->object;
118
    }
119
120
    public function getAttributeValue(string $attribute): mixed
121
    {
122
        if ($this->dataSetProvided) {
123
            return $this->object->getAttributeValue($attribute);
124
        }
125
126
        return $this->getData()[$attribute] ?? null;
127
    }
128
129
    public function hasAttribute(string $attribute): bool
130
    {
131
        return $this->dataSetProvided
132
            ? $this->object->hasAttribute($attribute)
133
            : array_key_exists($attribute, $this->getReflectionProperties());
134
    }
135
136
    public function getData(): array
137
    {
138
        if ($this->dataSetProvided) {
139
            return $this->object->getData();
140
        }
141
142
        $data = [];
143
        foreach ($this->getReflectionProperties() as $name => $property) {
144
            $data[$name] = $property->getValue($this->object);
145
        }
146
147
        return $data;
148
    }
149
150
    private function canCache(): bool
151
    {
152
        return !$this->object instanceof stdClass;
153
    }
154
155
    private function hasCacheItem(#[ExpectedValues(['rules', 'reflectionProperties'])] string $name): bool
156
    {
157
        if (!array_key_exists($this->cacheKey, self::$cache)) {
158
            return false;
159
        }
160
161
        return array_key_exists($name, self::$cache[$this->cacheKey]);
162
    }
163
164
    private function getCacheItem(#[ExpectedValues(['rules', 'reflectionProperties'])] string $name): array
165
    {
166
        return self::$cache[$this->cacheKey][$name];
167
    }
168
169
    private function setCacheItem(#[ExpectedValues(['rules', 'reflectionProperties'])] string $name, array $rules): void
170
    {
171
        self::$cache[$this->cacheKey][$name] = $rules;
172
    }
173
}
174