Passed
Pull Request — master (#373)
by Sergei
05:42 queued 02:46
created

ObjectParser::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 10
ccs 3
cts 4
cp 0.75
rs 10
cc 2
nc 2
nop 3
crap 2.0625
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
        bool $useCache = true
37
    ) {
38 52
        $this->cacheKey = $useCache
39 52
            ? $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 36
    public function getRules(): array
47
    {
48 36
        if ($this->hasCacheItem('rules')) {
49
            /** @var array<string, list<RuleInterface>> */
50 11
            return $this->getCacheItem('rules');
51
        }
52
53 33
        $rules = [];
54 33
        foreach ($this->getReflectionProperties() as $property) {
55
            // TODO: use Generator to collect attributes.
56 32
            $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
57 32
            foreach ($attributes as $attribute) {
58 30
                $rule = $attribute->newInstance();
59 30
                $rules[$property->getName()][] = $rule;
60
61 30
                if ($rule instanceof AfterInitAttributeEventInterface) {
62 3
                    $rule->afterInitAttribute($this->object);
63
                }
64
            }
65
        }
66
67 32
        if ($this->useCache()) {
68 32
            $this->setCacheItem('rules', $rules);
69
        }
70
71 32
        return $rules;
72
    }
73
74 38
    public function getAttributeValue(string $attribute): mixed
75
    {
76 38
        return ($this->getReflectionProperties()[$attribute] ?? null)?->getValue($this->object);
77
    }
78
79 28
    public function hasAttribute(string $attribute): bool
80
    {
81 28
        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 58
    private function getReflectionProperties(): array
99
    {
100 58
        if ($this->hasCacheItem('reflectionProperties')) {
101
            /** @var array<string, ReflectionProperty> */
102 43
            return $this->getCacheItem('reflectionProperties');
103
        }
104
105 44
        $reflection = new ReflectionObject($this->object);
106 44
        $reflectionProperties = [];
107
108 44
        foreach ($reflection->getProperties($this->propertyVisibility) as $property) {
109 42
            if (PHP_VERSION_ID < 80100) {
110 42
                $property->setAccessible(true);
111
            }
112
113 42
            $reflectionProperties[$property->getName()] = $property;
114
        }
115
116 44
        if ($this->useCache()) {
117 43
            $this->setCacheItem('reflectionProperties', $reflectionProperties);
118
        }
119
120 44
        return $reflectionProperties;
121
    }
122
123 58
    private function hasCacheItem(
124
        #[ExpectedValues(['rules', 'reflectionProperties'])]
125
        string $name
126
    ): bool {
127 58
        if (!$this->useCache()) {
128 1
            return false;
129
        }
130
131 57
        if (!array_key_exists($this->cacheKey, self::$cache)) {
132 43
            return false;
133
        }
134
135 43
        return array_key_exists($name, self::$cache[$this->cacheKey]);
136
    }
137
138 43
    private function getCacheItem(
139
        #[ExpectedValues(['rules', 'reflectionProperties'])]
140
        string $name
141
    ): array {
142
        /** @psalm-suppress PossiblyNullArrayOffset */
143 43
        return self::$cache[$this->cacheKey][$name];
144
    }
145
146 43
    private function setCacheItem(
147
        #[ExpectedValues(['rules', 'reflectionProperties'])]
148
        string $name,
149
        array $value
150
    ): void {
151
        /** @psalm-suppress PossiblyNullArrayOffset */
152 43
        self::$cache[$this->cacheKey][$name] = $value;
153
    }
154
155
    /**
156
     * @psalm-assert string $this->cacheKey
157
     */
158 58
    private function useCache(): bool
159
    {
160 58
        return $this->cacheKey !== null;
161
    }
162
}
163