Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
54.35% covered (warning)
54.35%
25 / 46
53.33% covered (warning)
53.33%
8 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
Field
54.35% covered (warning)
54.35%
25 / 46
53.33% covered (warning)
53.33%
8 / 15
78.80
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
 __toString
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 value
n/a
0 / 0
n/a
0 / 0
0
 structure
n/a
0 / 0
n/a
0 / 0
0
 shape
n/a
0 / 0
n/a
0 / 0
0
 isset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 initSchema
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 schemaRegistry
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 properties
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getFileStructure
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getSimpleStructure
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 addType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addMeta
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 metaShape
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 zxxShape
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 scalarValue
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 scalarValueMap
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 listValueMap
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Field;
6
7use Celemas\Sire\Extra;
8use Celemas\Sire\Shape;
9use Cosray\Field\Schema\Handler;
10use Cosray\Field\Schema\Registry;
11use Cosray\Validation\Shapes;
12use Cosray\Value\Value;
13use Cosray\Value\ValueContext;
14use ReflectionProperty;
15
16abstract class Field implements
17    Capability\Defaultable,
18    Capability\Describable,
19    Capability\Hidable,
20    Capability\Immutable,
21    Capability\Labelable,
22    Capability\Requirable,
23    Capability\Resizable,
24    Capability\Validatable
25{
26    use Capability\IsRequirable;
27    use Capability\IsLabelable;
28    use Capability\IsDescribable;
29    use Capability\IsHidable;
30    use Capability\IsImmutable;
31    use Capability\IsDefaultable;
32    use Capability\IsResizable;
33    use Capability\IsValidatable;
34
35    public const string NEUTRAL_LOCALE = 'zxx';
36
37    public readonly string $type;
38
39    /** @var list<array{object, Handler}> */
40    protected array $meta = [];
41
42    protected ?Registry $schemaRegistry = null;
43
44    final public function __construct(
45        public readonly string $name,
46        public readonly Owner $owner,
47        protected readonly ValueContext $valueContext,
48    ) {
49        $this->type = $this::class;
50    }
51
52    public function __toString(): string
53    {
54        return $this->value()->__toString();
55    }
56
57    abstract public function value(): Value;
58
59    abstract public function structure(mixed $value = null): array;
60
61    abstract public function shape(): Shape;
62
63    public function isset(): bool
64    {
65        return $this->value()->isset();
66    }
67
68    public function initSchema(ReflectionProperty $property, Registry $registry): void
69    {
70        $this->schemaRegistry = $registry;
71
72        foreach ($property->getAttributes() as $attr) {
73            $instance = $attr->newInstance();
74            $handler = $registry->getHandler($instance);
75
76            if ($handler === null) {
77                continue;
78            }
79
80            $handler->apply($instance, $this);
81            $this->meta[] = [$instance, $handler];
82        }
83    }
84
85    public function schemaRegistry(): Registry
86    {
87        return $this->schemaRegistry ??= Registry::withDefaults();
88    }
89
90    public function properties(): array
91    {
92        $properties = [
93            'name' => $this->name,
94            'type' => $this::class,
95        ];
96
97        foreach ($this->meta as [$meta, $handler]) {
98            $properties = array_merge($properties, $handler->properties($meta, $this));
99        }
100
101        return $properties;
102    }
103
104    public function getFileStructure(string $type, mixed $value = null): array
105    {
106        unset($type);
107
108        return [
109            'type' => $this::class,
110            'value' => $this->listValueMap($value),
111        ];
112    }
113
114    public function getSimpleStructure(string $type, mixed $value = null): array
115    {
116        unset($type);
117
118        return [
119            'type' => $this::class,
120            'value' => $this->scalarValueMap($value),
121        ];
122    }
123
124    protected function addType(Shape $shape): void
125    {
126        $shape->add('type', 'string')->rules('required', 'in:' . $this::class);
127    }
128
129    protected function addMeta(Shape $shape): void
130    {
131        $shape->add('meta', $this->metaShape())->optional()->nullable();
132    }
133
134    protected function metaShape(): Shape
135    {
136        return Shapes::create()->extra(Extra::Allow);
137    }
138
139    protected function zxxShape(string|Shape $valueShape, array $validators = []): Shape
140    {
141        $shape = Shapes::create();
142        $field = $shape->add(self::NEUTRAL_LOCALE, $valueShape)->rules(...$validators);
143
144        if (!$this->isRequired()) {
145            $field->optional()->nullable();
146        }
147
148        return $shape;
149    }
150
151    protected function scalarValue(mixed $value = null): mixed
152    {
153        return $value ?? $this->default;
154    }
155
156    protected function scalarValueMap(mixed $value = null): array
157    {
158        $value ??= $this->default;
159
160        if (is_array($value) && array_key_exists(self::NEUTRAL_LOCALE, $value)) {
161            return $value;
162        }
163
164        return [self::NEUTRAL_LOCALE => $value];
165    }
166
167    protected function listValueMap(mixed $value = null): array
168    {
169        $value ??= $this->default ?? [];
170
171        if (is_array($value) && array_key_exists(self::NEUTRAL_LOCALE, $value)) {
172            return $value;
173        }
174
175        return [self::NEUTRAL_LOCALE => is_array($value) ? $value : []];
176    }
177}