Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.93% covered (warning)
63.93%
39 / 61
38.46% covered (danger)
38.46%
5 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
OptionalField
63.93% covered (warning)
63.93%
39 / 61
38.46% covered (danger)
38.46%
5 / 13
115.06
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasDefaultValue
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
4.59
 getDefaultValue
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 allowsNull
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isRequired
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 isField
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 appliesToContext
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFieldPriority
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 setValue
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 markValueAsMissing
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getValue
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
4.94
 getTypehint
78.26% covered (warning)
78.26%
18 / 23
0.00% covered (danger)
0.00%
0 / 1
11.03
 getAttributes
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2namespace Apie\Core\Metadata\Fields;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Exceptions\InvalidTypeException;
6use Apie\Core\Metadata\GetterInterface;
7use Apie\Core\Metadata\SetterInterface;
8use Apie\TypeConverter\ReflectionTypeFactory;
9use ReflectionType;
10
11class OptionalField implements FieldWithPossibleDefaultValue, GetterInterface, SetterInterface
12{
13    public function __construct(private FieldInterface $field1, private ?FieldInterface $field2 = null)
14    {
15    }
16
17    public function hasDefaultValue(): bool
18    {
19        if ($this->field1 instanceof FieldWithPossibleDefaultValue && $this->field1->hasDefaultValue()) {
20            return true;
21        }
22
23        return $this->field2 instanceof FieldWithPossibleDefaultValue && $this->field2->hasDefaultValue();
24    }
25
26    public function getDefaultValue(): mixed
27    {
28        if ($this->field1 instanceof FieldWithPossibleDefaultValue && $this->field1->hasDefaultValue()) {
29            return $this->field1->getDefaultValue();
30        }
31        assert($this->field2 instanceof FieldWithPossibleDefaultValue);
32        return $this->field2->getDefaultValue();
33    }
34
35    public function allowsNull(): bool
36    {
37        if ($this->field2?->allowsNull()) {
38            return true;
39        }
40        return $this->field1->allowsNull();
41    }
42
43    public function isRequired(): bool
44    {
45        if ($this->field2 !== null && !$this->field2->isRequired()) {
46            return false;
47        }
48
49        return $this->field1->isRequired();
50    }
51
52    public function isField(): bool
53    {
54        if ($this->field2 !== null && !$this->field2->isField()) {
55            return false;
56        }
57        return $this->field1->isField();
58    }
59
60    public function appliesToContext(ApieContext $apieContext): bool
61    {
62        return $this->field1->appliesToContext($apieContext);
63    }
64
65    public function getFieldPriority(): ?int
66    {
67        if ($this->field2 === null) {
68            return $this->field1->getFieldPriority();
69        }
70        return min($this->field1->getFieldPriority(), $this->field2->getFieldPriority());
71    }
72
73    public function setValue(object $object, mixed $value, ApieContext $apieContext): void
74    {
75        if ($this->field1 instanceof SetterInterface) {
76            $this->field1->setValue($object, $value, $apieContext);
77        }
78        if ($this->field2 instanceof SetterInterface) {
79            $this->field2->setValue($object, $value, $apieContext);
80        }
81    }
82
83    public function markValueAsMissing(): void
84    {
85        if ($this->field1 instanceof SetterInterface) {
86            $this->field1->markValueAsMissing();
87        }
88        if ($this->field2 instanceof SetterInterface) {
89            $this->field2->markValueAsMissing();
90        }
91    }
92
93    public function getValue(object $object, ApieContext $apieContext): mixed
94    {
95        if ($this->field1 instanceof GetterInterface) {
96            return $this->field1->getValue($object, $apieContext);
97        }
98        if ($this->field2 instanceof GetterInterface) {
99            return $this->field2->getValue($object, $apieContext);
100        }
101
102        return null;
103    }
104
105    public function getTypehint(): ?ReflectionType
106    {
107        $type1 = $this->field1->getTypehint();
108        $type2 = $this->field2?->getTypehint();
109
110        if (null === $this->field2) {
111            return $this->field1->getTypehint();
112        }
113
114        if ($type1 === null || $type2 === null) {
115            return null;
116        }
117        $types = [];
118        $todo = [$type1, $type2];
119        while (!empty($todo)) {
120            $type = array_shift($todo);
121            if ($type instanceof \ReflectionUnionType) {
122                foreach ($type->getTypes() as $inner) {
123                    $todo[] = $inner;
124                }
125            } elseif ($type instanceof \ReflectionNamedType) {
126                $name = $type->getName();
127                if ($name === 'mixed') {
128                    return ReflectionTypeFactory::createReflectionType('mixed');
129                }
130                if ($type->allowsNull()) {
131                    $types['null'] = 'null';
132                }
133                $types[$name] = $name;
134            } else {
135                throw new InvalidTypeException($type, 'ReflectionNamedType|ReflectionUnionType');
136            }
137        }
138
139        $merged = implode('|', array_keys($types));
140
141        return ReflectionTypeFactory::createReflectionType($merged);
142    }
143
144    public function getAttributes(string $attributeClass, bool $classDocBlock = true, bool $propertyDocblock = true, bool $argumentDocBlock = true): array
145    {
146        $attributes = $this->field1->getAttributes($attributeClass, $classDocBlock, $propertyDocblock, $argumentDocBlock);
147        if (!empty($attributes)) {
148            return $attributes;
149        }
150        return $this->field2?->getAttributes($attributeClass, $classDocBlock, $propertyDocblock, $argumentDocBlock) ?? [];
151    }
152}