Lines
98.46%
64 / 65
Methods
92.85%
13 / 14
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| withResolvedReferences | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 2 | ||
| resolveKey | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 4 | ||
| resolveReferences | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 3 | ||
| toJsonObject | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 3 | ||
| setJsonObject | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| setDefaults | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| setJsonCompressLevel | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| toSanitizedArray | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 1 | ||
| toSanitizedValue | 100.00% | 13 / 13 | 100.00% | 1 / 1 | 7 | ||
| toSanitizedList | 100.00% | 9 / 9 | 100.00% | 1 / 1 | 7 | ||
| isItemEmpty | 100.00% | 4 / 4 | 100.00% | 1 / 1 | 3 | ||
| withoutRefs | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| getWithoutRefs | 83.33% | 5 / 6 | 0.00% | 0 / 1 | 4.07 | ||
| 8 | class 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 | } |