Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.46% covered (success)
98.46%
64 / 65
92.86% covered (success)
92.86%
13 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Schema
98.46% covered (success)
98.46%
64 / 65
92.86% covered (success)
92.86%
13 / 14
39
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 withResolvedReferences
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 resolveKey
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 resolveReferences
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 toJsonObject
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 setJsonObject
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setDefaults
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setJsonCompressLevel
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 toSanitizedArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 toSanitizedValue
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
 toSanitizedList
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 isItemEmpty
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 withoutRefs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWithoutRefs
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
1<?php
2
3namespace BO\Zmsentities\Schema;
4
5/**
6 * @extends \ArrayObject<array-key, mixed>
7 */
8class Schema extends \ArrayObject
9{
10    protected object|array|null $input = null;
11
12    protected ?\stdClass $asObject = null;
13
14    protected array $defaults = [];
15
16    /**
17     * @var Int $jsonCompressLevel
18     */
19    protected $jsonCompressLevel = 0;
20
21    /**
22     * Read the json schema and let array act like an object
23     */
24    public function __construct(object|array $input = [], int $flags = \ArrayObject::ARRAY_AS_PROPS, string $iterator_class = "ArrayIterator")
25    {
26        $this->input = $input;
27        /** @var class-string<\ArrayIterator> $iterator_class */
28        parent::__construct($input, $flags, $iterator_class);
29    }
30
31    public function withResolvedReferences(mixed $resolveLevel): mixed
32    {
33        if ($resolveLevel > 0) {
34            $schema = clone $this;
35            $schema = $this->resolveReferences($schema, $resolveLevel);
36            $schema->setJsonObject(json_decode((string) json_encode($schema->toSanitizedArray(true))));
37            return $schema;
38        }
39        return $this;
40    }
41
42    /**
43     * @param (int|string) $key
44     *
45     * @psalm-param array-key $key
46     */
47    protected function resolveKey($key, mixed $value, mixed $resolveLevel): mixed
48    {
49        if (is_array($value)) {
50            $value = $this->resolveReferences($value, $resolveLevel);
51        } elseif ($key === '$ref' && $value[0] != '#') {
52            $value = Loader::asArray($value)->withResolvedReferences($resolveLevel - 1);
53        }
54        return $value;
55    }
56
57    protected function resolveReferences(array|self $hash, mixed $resolveLevel): mixed
58    {
59        foreach ($hash as $key => $value) {
60            $hash[$key] = $this->resolveKey($key, $value, $resolveLevel);
61            if ($hash[$key] instanceof self) {
62                // Schema from Loader::asArray() is returned, we guess $key is '$ref' and should be replaced
63                return $hash[$key]->getArrayCopy();
64            }
65        }
66        return $hash;
67    }
68
69    public function toJsonObject(bool $keepEmpty = false): mixed
70    {
71        if (null !== $this->asObject) {
72            $data = $this->asObject;
73        } elseif (! $keepEmpty) {
74            $data = json_decode((string) json_encode($this->toSanitizedArray($keepEmpty)));
75        } else {
76            $data = json_decode((string) json_encode($this->toSanitizedArray($keepEmpty)));
77        }
78        return $data;
79    }
80
81    public function setJsonObject(\stdClass $asObject): static
82    {
83        $this->asObject = $asObject;
84        return $this;
85    }
86
87    public function setDefaults(array $defaults): void
88    {
89        $this->defaults = $defaults;
90    }
91
92    public function setJsonCompressLevel(int $jsonCompressLevel): static
93    {
94        $this->jsonCompressLevel = $jsonCompressLevel;
95        return $this;
96    }
97
98    public function toSanitizedArray(bool $keepEmpty = false): mixed
99    {
100        $data = $this->getArrayCopy();
101        $data = $this->toSanitizedValue($data, $keepEmpty, $this->defaults);
102        return $data;
103    }
104
105    /**
106     * Sanitize value for valid export as JSON
107     *
108     */
109    protected function toSanitizedValue(mixed $value, bool $keepEmpty = false, mixed $defaults = []): mixed
110    {
111        if ($value instanceof \BO\Zmsentities\Helper\NoSanitize) {
112            return $value;
113        }
114        if ($value instanceof \BO\Zmsentities\Collection\JsonUnindexed) {
115            $value = array_values($value->getArrayCopy());
116        } elseif ($value instanceof \ArrayObject) {
117            if (method_exists($value, 'getDefaults')) {
118                $defaults = $value->getDefaults();
119            }
120            $value = $value->getArrayCopy();
121        }
122        if (is_array($value)) {
123            $value = $this->toSanitizedList($value, $keepEmpty, $defaults);
124        }
125        if ($value instanceof \JsonSerializable) {
126            $value = $value->jsonSerialize();
127        }
128        return $value;
129    }
130
131    protected function toSanitizedList(array $value, mixed $keepEmpty, mixed $defaults = []): mixed
132    {
133        foreach ($value as $key => $item) {
134            if ($this->jsonCompressLevel > 0 && isset($defaults[$key])) {
135                $value[$key] = $this->toSanitizedValue($item, $keepEmpty, $defaults[$key]);
136                if ($defaults[$key] === $value[$key]) {
137                    $value[$key] = null;
138                }
139            } else {
140                $value[$key] = $this->toSanitizedValue($item, $keepEmpty);
141            }
142            if (! $keepEmpty && $this->isItemEmpty($value[$key])) {
143                unset($value[$key]);
144            }
145        }
146        return $value;
147    }
148
149    protected static function isItemEmpty(mixed $item): bool
150    {
151        return (
152            null === $item
153            || (is_array($item) && count($item) == 0)
154        );
155    }
156
157    /*
158     * Uses a path like '/changePassword/0' to fetch property settings
159     *
160    public function toProperty()
161    {
162        return new \BO\Zmsentities\Helper\Property($this);
163    }
164
165
166    public function getPropertyByPath($path)
167    {
168        $pointerList = explode('/', trim($path, '/ '));
169        $property = $this->toProperty();
170        $property = $pointerList[0] == 'properties' ? $property : $property->properties;
171        foreach ($pointerList as $pointer) {
172            if ($property->type->get() == 'array') {
173                $property = $property['items'];
174            } elseif ($property->type->get() == 'object' && $pointer !== 'properties') {
175                $property = $property->properties[$pointer];
176            } elseif (is_numeric($pointer)) {
177                // ignore array items
178            } else {
179                $property = $property[$pointer];
180            }
181        }
182        return $property->get([]);
183    }
184    */
185
186    public function withoutRefs(): mixed
187    {
188        return $this->getWithoutRefs(clone $this);
189    }
190
191    protected function getWithoutRefs(array|self $data): mixed
192    {
193        foreach ($data as $key => $value) {
194            if (is_array($value)) {
195                $data[$key] = $this->getWithoutRefs($value);
196            }
197            if ($key === '$ref') {
198                unset($data[$key]);
199            }
200        }
201        return $data;
202    }
203}