Passed
Pull Request — master (#373)
by Sergei
04:07 queued 01:10
created

ObjectParser::setCacheItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 7
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Helper;
6
7
use JetBrains\PhpStorm\ArrayShape;
8
use JetBrains\PhpStorm\ExpectedValues;
9
use ReflectionAttribute;
10
use ReflectionObject;
11
use ReflectionProperty;
12
use Yiisoft\Validator\AfterInitAttributeEventInterface;
13
use Yiisoft\Validator\RuleInterface;
14
15
use function array_key_exists;
16
17
final class ObjectParser
18
{
19
    /**
20
     * @psalm-var array<string, array<string, array>>
21
     */
22
    #[ArrayShape([
23
        [
24
            'rules' => 'array',
25
            'reflectionAttributes' => 'array',
26
        ],
27
    ])]
28
    private static array $cache = [];
29
    private string|null $cacheKey;
30
31 52
    public function __construct(
32
        private object $object,
33
        private int $propertyVisibility = ReflectionProperty::IS_PRIVATE |
34
        ReflectionProperty::IS_PROTECTED |
35
        ReflectionProperty::IS_PUBLIC,
36
        private bool $skipStaticProperties = false,
37
        bool $useCache = true
38
    ) {
39 52
        $this->cacheKey = $useCache
40 52
            ? $this->object::class . '_' . $this->propertyVisibility
41
            : null;
42
    }
43
44
    /**
45
     * @return array<string, list<RuleInterface>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, list<RuleInterface>> at position 4 could not be parsed: Expected '>' at position 4, but found 'list'.
Loading history...
46
     */
47 36
    public function getRules(): array
48
    {
49 36
        if ($this->hasCacheItem('rules')) {
50
            /** @var array<string, list<RuleInterface>> */
51 10
            return $this->getCacheItem('rules');
52
        }
53
54 34
        $rules = [];
55 34
        foreach ($this->getReflectionProperties() as $property) {
56
            // TODO: use Generator to collect attributes.
57 33
            $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
58 33
            foreach ($attributes as $attribute) {
59 31
                $rule = $attribute->newInstance();
60 31
                $rules[$property->getName()][] = $rule;
61
62 31
                if ($rule instanceof AfterInitAttributeEventInterface) {
63 3
                    $rule->afterInitAttribute($this->object);
64
                }
65
            }
66
        }
67
68 33
        if ($this->useCache()) {
69 33
            $this->setCacheItem('rules', $rules);
70
        }
71
72 33
        return $rules;
73
    }
74
75 38
    public function getAttributeValue(string $attribute): mixed
76
    {
77 38
        return ($this->getReflectionProperties()[$attribute] ?? null)?->getValue($this->object);
78
    }
79
80 28
    public function hasAttribute(string $attribute): bool
81
    {
82 28
        return array_key_exists($attribute, $this->getReflectionProperties());
83
    }
84
85 21
    public function getData(): array
86
    {
87 21
        $data = [];
88 21
        foreach ($this->getReflectionProperties() as $name => $property) {
89
            /** @var mixed */
90 17
            $data[$name] = $property->getValue($this->object);
91
        }
92
93 21
        return $data;
94
    }
95
96
    /**
97
     * @return array<string, ReflectionProperty>
98
     */
99 58
    public function getReflectionProperties(): array
100
    {
101 58
        if ($this->hasCacheItem('reflectionProperties')) {
102
            /** @var array<string, ReflectionProperty> */
103 43
            return $this->getCacheItem('reflectionProperties');
104
        }
105
106 45
        $reflection = new ReflectionObject($this->object);
107 45
        $reflectionProperties = [];
108
109 45
        foreach ($reflection->getProperties($this->propertyVisibility) as $property) {
110 43
            if ($this->skipStaticProperties && $property->isStatic()) {
111
                continue;
112
            }
113
114 43
            if (PHP_VERSION_ID < 80100) {
115 43
                $property->setAccessible(true);
116
            }
117
118 43
            $reflectionProperties[$property->getName()] = $property;
119
        }
120
121 45
        if ($this->useCache()) {
122 44
            $this->setCacheItem('reflectionProperties', $reflectionProperties);
123
        }
124
125 45
        return $reflectionProperties;
126
    }
127
128 58
    private function hasCacheItem(
129
        #[ExpectedValues(['rules', 'reflectionProperties'])]
130
        string $name
131
    ): bool {
132 58
        if (!$this->useCache()) {
133 1
            return false;
134
        }
135
136 57
        if (!array_key_exists($this->cacheKey, self::$cache)) {
137 44
            return false;
138
        }
139
140 43
        return array_key_exists($name, self::$cache[$this->cacheKey]);
141
    }
142
143 43
    private function getCacheItem(
144
        #[ExpectedValues(['rules', 'reflectionProperties'])]
145
        string $name
146
    ): array {
147
        /** @psalm-suppress PossiblyNullArrayOffset */
148 43
        return self::$cache[$this->cacheKey][$name];
149
    }
150
151 45
    private function setCacheItem(
152
        #[ExpectedValues(['rules', 'reflectionProperties'])]
153
        string $name,
154
        array $value
155
    ): void {
156
        /** @psalm-suppress PossiblyNullArrayOffset */
157 45
        self::$cache[$this->cacheKey][$name] = $value;
158
    }
159
160
    /**
161
     * @psalm-assert string $this->cacheKey
162
     */
163 58
    private function useCache(): bool
164
    {
165 58
        return $this->cacheKey !== null;
166
    }
167
}
168