Passed
Pull Request — master (#373)
by Sergei
12:00
created

ObjectParser   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 149
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 25
eloc 48
c 2
b 0
f 0
dl 0
loc 149
ccs 53
cts 53
cp 1
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A useCache() 0 3 1
A hasCacheItem() 0 13 3
A getData() 0 9 2
A getAttributeValue() 0 3 1
A hasAttribute() 0 3 1
A setCacheItem() 0 7 1
A getRules() 0 26 6
A getCacheItem() 0 6 1
A __construct() 0 11 2
B getReflectionProperties() 0 27 7
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
     * @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 61
    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 61
        $this->cacheKey = $useCache
40 60
            ? $this->object::class . '_' . $this->propertyVisibility . '_' . $this->skipStaticProperties
41 1
            : 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 38
    public function getRules(): array
48
    {
49 38
        if ($this->hasCacheItem('rules')) {
50
            /** @var array<string, list<RuleInterface>> */
51 8
            return $this->getCacheItem('rules');
52
        }
53
54 32
        $rules = [];
55 32
        foreach ($this->getReflectionProperties() as $property) {
56
            // TODO: use Generator to collect attributes.
57 31
            $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
58 31
            foreach ($attributes as $attribute) {
59 29
                $rule = $attribute->newInstance();
60 29
                $rules[$property->getName()][] = $rule;
61
62 29
                if ($rule instanceof AfterInitAttributeEventInterface) {
63 3
                    $rule->afterInitAttribute($this->object);
64
                }
65
            }
66
        }
67
68 31
        if ($this->useCache()) {
69 31
            $this->setCacheItem('rules', $rules);
70
        }
71
72 31
        return $rules;
73
    }
74
75 40
    public function getAttributeValue(string $attribute): mixed
76
    {
77 40
        return ($this->getReflectionProperties()[$attribute] ?? null)?->getValue($this->object);
78
    }
79
80 30
    public function hasAttribute(string $attribute): bool
81
    {
82 30
        return array_key_exists($attribute, $this->getReflectionProperties());
83
    }
84
85 26
    public function getData(): array
86
    {
87 26
        $data = [];
88 26
        foreach ($this->getReflectionProperties() as $name => $property) {
89
            /** @var mixed */
90 22
            $data[$name] = $property->getValue($this->object);
91
        }
92
93 26
        return $data;
94
    }
95
96
    /**
97
     * @return array<string, ReflectionProperty>
98
     */
99 65
    public function getReflectionProperties(): array
100
    {
101 65
        if ($this->hasCacheItem('reflectionProperties')) {
102
            /** @var array<string, ReflectionProperty> */
103 46
            return $this->getCacheItem('reflectionProperties');
104
        }
105
106 47
        $reflection = new ReflectionObject($this->object);
107 47
        $reflectionProperties = [];
108
109 47
        foreach ($reflection->getProperties($this->propertyVisibility) as $property) {
110 45
            if ($this->skipStaticProperties && $property->isStatic()) {
111 1
                continue;
112
            }
113
114 45
            if (PHP_VERSION_ID < 80100) {
115 45
                $property->setAccessible(true);
116
            }
117
118 45
            $reflectionProperties[$property->getName()] = $property;
119
        }
120
121 47
        if ($this->useCache()) {
122 45
            $this->setCacheItem('reflectionProperties', $reflectionProperties);
123
        }
124
125 47
        return $reflectionProperties;
126
    }
127
128 65
    private function hasCacheItem(
129
        #[ExpectedValues(['rules', 'reflectionProperties'])]
130
        string $name
131
    ): bool {
132 65
        if (!$this->useCache()) {
133 2
            return false;
134
        }
135
136 63
        if (!array_key_exists($this->cacheKey, self::$cache)) {
137 45
            return false;
138
        }
139
140 46
        return array_key_exists($name, self::$cache[$this->cacheKey]);
141
    }
142
143 46
    private function getCacheItem(
144
        #[ExpectedValues(['rules', 'reflectionProperties'])]
145
        string $name
146
    ): array {
147
        /** @psalm-suppress PossiblyNullArrayOffset */
148 46
        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 65
    private function useCache(): bool
164
    {
165 65
        return $this->cacheKey !== null;
166
    }
167
}
168