Passed
Pull Request — master (#323)
by
unknown
02:47
created

ObjectDataSet::getCachedRules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\DataSet;
6
7
use ReflectionAttribute;
8
use ReflectionObject;
9
use ReflectionProperty;
10
use Yiisoft\Validator\AttributeEventInterface;
11
use Yiisoft\Validator\DataSetInterface;
12
use Yiisoft\Validator\RuleInterface;
13
use Yiisoft\Validator\RulesProviderInterface;
14
15
use function array_key_exists;
16
use function get_class;
17
18
/**
19
 * This data set makes use of attributes introduced in PHP 8. It simplifies rules configuration process, especially for
20
 * nested data and relations. Please refer to the guide for examples.
21
 *
22
 * @link https://www.php.net/manual/en/language.attributes.overview.php
23
 */
24
final class ObjectDataSet implements RulesProviderInterface, DataSetInterface
25
{
26
    private bool $dataSetProvided;
27
    /**
28
     * @var ReflectionProperty[]
29
     */
30
    private ?array $reflectionProperties = null;
31
    private ?array $data = null;
32
33
    private static array $cachedRules = [];
34
    private string $cacheKey;
35
36 49
    public function __construct(
37
        private object $object,
38
        private int $propertyVisibility = ReflectionProperty::IS_PRIVATE |
39
        ReflectionProperty::IS_PROTECTED |
40
        ReflectionProperty::IS_PUBLIC
41
    ) {
42 49
        $this->dataSetProvided = $this->object instanceof DataSetInterface;
43 49
        $this->cacheKey = get_class($this->object) . '_' . $this->propertyVisibility;
44
    }
45
46 54
    public function getRules(): iterable
47
    {
48 54
        $objectHasRules = $this->object instanceof RulesProviderInterface;
49 54
        $rules = $objectHasRules ? $this->object->getRules() : [];
50
51
        // Providing data set assumes object has its own attributes and rules getting logic. So further parsing of
52
        // Reflection properties and rules is skipped intentionally.
53 54
        if ($this->dataSetProvided || $objectHasRules === true) {
54 18
            return $rules;
55
        }
56
57 36
        if ($this->hasCachedRules()) {
58 9
            return $this->getCachedRules();
59
        }
60
61 35
        foreach ($this->getReflectionProperties() as $property) {
62 34
            $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
63 34
            foreach ($attributes as $attribute) {
64 32
                $rule = $attribute->newInstance();
65 32
                $rules[$property->getName()][] = $rule;
66
67 32
                if ($rule instanceof AttributeEventInterface) {
68 4
                    $rule->afterInitAttribute($this);
69
                }
70
            }
71
        }
72
73 32
        $this->setCachedRules($rules);
74
75 32
        return $rules;
76
    }
77
78
    /**
79
     * @return ReflectionProperty[] Used to avoid error "Typed property must not be accessed before initialization".
80
     */
81 56
    private function getReflectionProperties(): array
82
    {
83 56
        if ($this->reflectionProperties !== null) {
84 31
            return $this->reflectionProperties;
85
        }
86
87
        // Providing data set assumes object has its own attributes and rules getting logic. So further parsing of
88
        // Reflection properties and rules is skipped intentionally.
89 56
        if ($this->dataSetProvided) {
90
            $this->reflectionProperties = [];
91
92
            return $this->reflectionProperties;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->reflectionProperties returns the type null which is incompatible with the type-hinted return array.
Loading history...
93
        }
94
95
        // TODO: Use Generator to collect attributes
96
97 56
        $reflection = new ReflectionObject($this->object);
98 56
        $reflectionProperties = [];
99
100 56
        foreach ($reflection->getProperties($this->propertyVisibility) as $property) {
101 52
            if (PHP_VERSION_ID < 80100) {
102 52
                $property->setAccessible(true);
103
            }
104
105 52
            $reflectionProperties[$property->getName()] = $property;
106
        }
107
108 56
        $this->reflectionProperties = $reflectionProperties;
109
110 56
        return $reflectionProperties;
111
    }
112
113 4
    public function getObject(): object
114
    {
115 4
        return $this->object;
116
    }
117
118 44
    public function getAttributeValue(string $attribute): mixed
119
    {
120 44
        if ($this->dataSetProvided) {
121 8
            return $this->object->getAttributeValue($attribute);
122
        }
123
124 36
        return $this->getData()[$attribute] ?? null;
125
    }
126
127 26
    public function hasAttribute(string $attribute): bool
128
    {
129 26
        return $this->dataSetProvided
130
            ? $this->object->hasAttribute($attribute)
131 26
            : array_key_exists($attribute, $this->getReflectionProperties());
132
    }
133
134 57
    public function getData(): array
135
    {
136 57
        if ($this->dataSetProvided) {
137 12
            return $this->object->getData();
138
        }
139
140 45
        if ($this->data !== null) {
141 17
            return $this->data;
142
        }
143
144 44
        $data = [];
145 44
        foreach ($this->getReflectionProperties() as $name => $property) {
146 41
            $data[$name] = $property->getValue($this->object);
147
        }
148
149 44
        $this->data = $data;
150
151 44
        return $data;
152
    }
153
154 36
    private function hasCachedRules(): bool
155
    {
156 36
        return array_key_exists($this->cacheKey, self::$cachedRules);
157
    }
158
159 9
    private function getCachedRules(): array
160
    {
161 9
        return self::$cachedRules[$this->cacheKey];
162
    }
163
164 32
    private function setCachedRules(array $rules): void
165
    {
166 32
        self::$cachedRules[$this->cacheKey] = $rules;
167
    }
168
}
169