Passed
Pull Request — master (#323)
by Dmitriy
02:46
created

ObjectDataSet::getObject()   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\ObjectDataSetInterface;
13
use Yiisoft\Validator\RuleInterface;
14
use Yiisoft\Validator\RulesProviderInterface;
15
16
use function array_key_exists;
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, ObjectDataSetInterface
25
{
26
    private bool $dataSetProvided;
27
    /**
28
     * @var ReflectionProperty[]
29
     */
30
    private ?array $reflectionProperties = null;
31
    private ?iterable $rules = null;
32
    private ?array $data = null;
33
34 39
    public function __construct(
35
        private object $object,
36
        private int $propertyVisibility = ReflectionProperty::IS_PRIVATE |
37
        ReflectionProperty::IS_PROTECTED |
38
        ReflectionProperty::IS_PUBLIC
39
    ) {
40 39
        $this->dataSetProvided = $this->object instanceof DataSetInterface;
41
    }
42
43 58
    public function getRules(): iterable
44
    {
45 58
        if ($this->rules !== null) {
46 9
            return $this->rules;
47
        }
48
49 55
        $objectHasRules = $this->object instanceof RulesProviderInterface;
50 55
        $rules = $objectHasRules ? $this->object->getRules() : [];
51
52
        // Providing data set assumes object has its own attributes and rules getting logic. So further parsing of
53
        // Reflection properties and rules is skipped intentionally.
54 55
        if ($this->dataSetProvided || $objectHasRules === true) {
55 19
            $this->rules = $rules;
56
57 19
            return $rules;
58
        }
59
60 36
        foreach ($this->getReflectionProperties() as $property) {
61 35
            $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF);
62 35
            foreach ($attributes as $attribute) {
63 33
                $rule = $attribute->newInstance();
64 33
                $rules[$property->getName()][] = $rule;
65
66 33
                if ($rule instanceof AttributeEventInterface) {
67 4
                    $rule->afterInitAttribute($this);
68
                }
69
            }
70
        }
71
72 33
        $this->rules = $rules;
73
74 33
        return $rules;
75
    }
76
77
    /**
78
     * @return ReflectionProperty[] Used to avoid error "Typed property must not be accessed before initialization".
79
     */
80 48
    public function getReflectionProperties(): array
81
    {
82 48
        if ($this->reflectionProperties !== null) {
83 30
            return $this->reflectionProperties;
84
        }
85
86
        // Providing data set assumes object has its own attributes and rules getting logic. So further parsing of
87
        // Reflection properties and rules is skipped intentionally.
88 48
        if ($this->dataSetProvided) {
89
            $this->reflectionProperties = [];
90
91
            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...
92
        }
93
94
        // TODO: Use Generator to collect attributes
95
96 48
        $reflection = new ReflectionObject($this->object);
97 48
        $reflectionProperties = [];
98
99 48
        foreach ($reflection->getProperties($this->propertyVisibility) as $property) {
100 47
            if (PHP_VERSION_ID < 80100) {
101 47
                $property->setAccessible(true);
102
            }
103
104 47
            $reflectionProperties[$property->getName()] = $property;
105
        }
106
107 48
        $this->reflectionProperties = $reflectionProperties;
108
109 48
        return $reflectionProperties;
110
    }
111
112 12
    public function getPropertyVisibility(): int
113
    {
114 12
        return $this->propertyVisibility;
115
    }
116
117 9
    public function getObject(): object
118
    {
119 9
        return $this->object;
120
    }
121
122 48
    public function getAttributeValue(string $attribute): mixed
123
    {
124 48
        if ($this->dataSetProvided) {
125 12
            return $this->object->getAttributeValue($attribute);
126
        }
127
128 36
        return $this->getData()[$attribute] ?? null;
129
    }
130
131 24
    public function hasAttribute(string $attribute): bool
132
    {
133 24
        return $this->dataSetProvided
134
            ? $this->object->hasAttribute($attribute)
135 24
            : array_key_exists($attribute, $this->getReflectionProperties());
136
    }
137
138 56
    public function getData(): array
139
    {
140 56
        if ($this->data !== null) {
141 22
            return $this->data;
142
        }
143
144 53
        if ($this->dataSetProvided) {
145 17
            $data = $this->object->getData();
146
        } else {
147 36
            $data = [];
148 36
            foreach ($this->getReflectionProperties() as $name => $property) {
149 36
                $data[$name] = $property->getValue($this->object);
150
            }
151
        }
152
153 53
        $this->data = $data;
154
155 53
        return $data;
156
    }
157
}
158