Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.91% |
60 / 66 |
|
71.43% |
10 / 14 |
CRAP | |
0.00% |
0 / 1 |
PublicProperty | |
90.91% |
60 / 66 |
|
71.43% |
10 / 14 |
50.80 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
10 | |||
findPromotedProperty | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
8.06 | |||
hasDefaultValue | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
8 | |||
getDefaultValue | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
allowsNull | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
isRequired | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isField | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
appliesToContext | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFieldPriority | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
2.26 | |||
getValue | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setValue | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
5 | |||
markValueAsMissing | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTypehint | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getAttributes | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
6.40 |
1 | <?php |
2 | namespace Apie\Core\Metadata\Fields; |
3 | |
4 | use Apie\Core\Attributes\ColumnPriority; |
5 | use Apie\Core\Attributes\Optional; |
6 | use Apie\Core\Context\ApieContext; |
7 | use Apie\Core\Enums\DoNotChangeUploadedFile; |
8 | use Apie\Core\Metadata\GetterInterface; |
9 | use Apie\Core\Metadata\SetterInterface; |
10 | use Apie\Core\Utils\ConverterUtils; |
11 | use Apie\TypeConverter\ReflectionTypeFactory; |
12 | use ReflectionProperty; |
13 | use ReflectionType; |
14 | |
15 | final class PublicProperty implements FieldWithPossibleDefaultValue, GetterInterface, SetterInterface |
16 | { |
17 | private bool $required; |
18 | |
19 | private bool $field; |
20 | |
21 | private bool $defaultValueAvailable; |
22 | private mixed $defaultValue; |
23 | |
24 | public function __construct( |
25 | private readonly ReflectionProperty $property, |
26 | bool $optional = false, |
27 | private bool $setterHooks = false, |
28 | ) { |
29 | $hasDefaultValue = $this->hasDefaultValue(); |
30 | $this->field = 'never' !== (string) $this->property->getType(); |
31 | if (PHP_VERSION_ID >= 80400) { |
32 | if ($this->setterHooks) { |
33 | $settableType = $this->property->getSettableType(); |
34 | if ('never' === (string) $settableType) { |
35 | $this->field = false; |
36 | } |
37 | } elseif (null === $this->property->getHook(\PropertyHookType::Get) |
38 | && null !== $this->property->getHook(\PropertyHookType::Set) |
39 | && $this->property->isVirtual()) { |
40 | $this->field = false; |
41 | } |
42 | } |
43 | |
44 | $this->required = !$optional |
45 | && empty($property->getAttributes(Optional::class)) |
46 | && !$hasDefaultValue |
47 | && $this->field; |
48 | } |
49 | |
50 | /** |
51 | * ReflectionProperty::hasDefaultValue() returns false for promoted public properties, |
52 | * so we look up the constructors to find ReflectionParameter and return this. |
53 | * @param \ReflectionClass<object> $class |
54 | */ |
55 | private function findPromotedProperty(\ReflectionClass $class): ?\ReflectionParameter |
56 | { |
57 | foreach ($class->getConstructor()->getParameters() as $parameter) { |
58 | if ($parameter->isPromoted() |
59 | && $parameter->isDefaultValueAvailable() |
60 | && $parameter->name === $this->property->name |
61 | ) { |
62 | return $parameter; |
63 | } |
64 | } |
65 | if (!$this->property->isPrivate()) { |
66 | $parentClass = $class->getConstructor()->getDeclaringClass()->getParentClass(); |
67 | if ($parentClass && $parentClass->name !== $class->name) { |
68 | return $this->findPromotedProperty($parentClass); |
69 | } |
70 | } |
71 | return null; |
72 | } |
73 | |
74 | public function hasDefaultValue(): bool |
75 | { |
76 | if (!isset($this->defaultValueAvailable)) { |
77 | $this->defaultValueAvailable = $this->property->hasDefaultValue(); |
78 | // if there is no typehint hasDefaultValue() always returns true |
79 | if (null === $this->property->getType()) { |
80 | $this->defaultValueAvailable = $this->property->getDefaultValue() !== null; |
81 | } |
82 | if ($this->defaultValueAvailable) { |
83 | $this->defaultValue = $this->property->getDefaultValue(); |
84 | } elseif ($this->setterHooks && $this->property->isPromoted()) { |
85 | $argument = $this->findPromotedProperty($this->property->getDeclaringClass()); |
86 | if ($argument && $argument->isDefaultValueAvailable()) { |
87 | $this->defaultValueAvailable = true; |
88 | $this->defaultValue = $argument->getDefaultValue(); |
89 | } |
90 | } |
91 | |
92 | } |
93 | return $this->defaultValueAvailable; |
94 | } |
95 | |
96 | public function getDefaultValue(): mixed |
97 | { |
98 | $this->hasDefaultValue(); |
99 | return $this->defaultValue; |
100 | } |
101 | |
102 | public function allowsNull(): bool |
103 | { |
104 | $type = $this->getTypehint(); |
105 | return $type === null || $type->allowsNull(); |
106 | } |
107 | |
108 | public function isRequired(): bool |
109 | { |
110 | return $this->required; |
111 | } |
112 | |
113 | public function isField(): bool |
114 | { |
115 | return $this->field; |
116 | } |
117 | |
118 | public function appliesToContext(ApieContext $apieContext): bool |
119 | { |
120 | return $apieContext->appliesToContext($this->property); |
121 | } |
122 | |
123 | public function getFieldPriority(): ?int |
124 | { |
125 | $attributes = $this->property->getAttributes(ColumnPriority::class); |
126 | if (empty($attributes)) { |
127 | return null; |
128 | } |
129 | |
130 | $attribute = reset($attributes); |
131 | return $attribute->newInstance()->priority; |
132 | } |
133 | |
134 | public function getValue(object $object, ApieContext $apieContext): mixed |
135 | { |
136 | return $this->property->getValue($object); |
137 | } |
138 | |
139 | public function setValue(object $object, mixed $value, ApieContext $apieContext): void |
140 | { |
141 | if ($value !== DoNotChangeUploadedFile::DoNotChange && $this->field) { |
142 | if (!$this->property->isInitialized($object) || !$this->property->isReadOnly()) { |
143 | $this->property->setValue($object, $value); |
144 | } |
145 | } |
146 | } |
147 | |
148 | public function markValueAsMissing(): void |
149 | { |
150 | } |
151 | |
152 | public function getTypehint(): ?ReflectionType |
153 | { |
154 | if (!$this->field) { |
155 | return ReflectionTypeFactory::createReflectionType('never'); |
156 | } |
157 | return $this->property->getType(); |
158 | } |
159 | |
160 | public function getAttributes(string $attributeClass, bool $classDocBlock = true, bool $propertyDocblock = true, bool $argumentDocBlock = true): array |
161 | { |
162 | $list = []; |
163 | if ($propertyDocblock) { |
164 | foreach ($this->property->getAttributes($attributeClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { |
165 | $list[] = $attribute->newInstance(); |
166 | } |
167 | } |
168 | $class = ConverterUtils::toReflectionClass($this->property); |
169 | if ($class && $classDocBlock) { |
170 | foreach ($class->getAttributes($attributeClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { |
171 | $list[] = $attribute->newInstance(); |
172 | } |
173 | } |
174 | return $list; |
175 | } |
176 | } |