Passed
Pull Request — master (#373)
by Sergei
03:58
created

ObjectParser   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 144
Duplicated Lines 0 %

Test Coverage

Coverage 98.04%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 23
eloc 46
c 1
b 0
f 0
dl 0
loc 144
ccs 50
cts 51
cp 0.9804
rs 10

10 Methods

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