Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.79% covered (success)
92.79%
103 / 111
89.66% covered (warning)
89.66%
26 / 29
CRAP
0.00% covered (danger)
0.00%
0 / 1
Entity
92.79% covered (success)
92.79%
103 / 111
89.66% covered (warning)
89.66%
26 / 29
54.05
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 exchangeArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getUnflattenedArray
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getDefaults
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValidator
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isValid
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 testValid
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 createExample
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getExample
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 readJsonSchema
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getEntityName
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 setJsonCompressLevel
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 jsonSerialize
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __clone
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 addData
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 withData
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 hasId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 toProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getProperty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withProperty
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 withLessData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withCleanedUpFormData
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getResolveLevel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setResolveLevel
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 withResolveLevel
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 withReference
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace BO\Zmsentities\Schema;
4
5use BO\Zmsentities\Helper\Property;
6
7/**
8 * @SuppressWarnings(NumberOfChildren)
9 * @SuppressWarnings(PublicMethod)
10 * @SuppressWarnings(Complexity)
11 *
12 * @extends \ArrayObject<array-key, mixed>
13 */
14class Entity extends \ArrayObject implements \JsonSerializable
15{
16    /**
17     * primary id for entity
18     *
19     * @var string
20     */
21    public const string PRIMARY = 'id';
22
23    /**
24     * @var String $schema Filename of JSON-Schema file
25     */
26    public static $schema = null;
27
28    /**
29     * @var String $schema Filename of JSON-Schema file
30     */
31    public static $schemaRefPrefix = '';
32
33    /**
34     * @var \ArrayObject $jsonSchema JSON-Schema definition to validate data
35     */
36    protected $jsonSchema = null;
37
38    /**
39     * @var Int $jsonCompressLevel
40     */
41    protected $jsonCompressLevel = 0;
42
43    /**
44     * @var Array $schemaCache for not loading and interpreting a schema twice
45     *
46     */
47    protected static $schemaCache = [];
48
49    /**
50     * @var Int $resolveLevel indicator on data integrity
51     */
52    protected $resolveLevel = null;
53
54    /**
55     * Read the json schema and let array act like an object
56     */
57    public function __construct($input = null, $flags = \ArrayObject::ARRAY_AS_PROPS, $iterator_class = "ArrayIterator")
58    {
59        parent::__construct($this->getDefaults(), $flags, $iterator_class);
60        if ($input) {
61            $input = $this->getUnflattenedArray($input);
62            $this->addData($input);
63        }
64    }
65
66    #[\Override]
67    public function exchangeArray(object|array $input): array
68    {
69        parent::exchangeArray($this->getDefaults());
70        $input = $this->getUnflattenedArray($input);
71        $this->addData($input);
72        return $this->getArrayCopy();
73    }
74
75    public function getUnflattenedArray($input)
76    {
77        if (!$input instanceof UnflattedArray) {
78            $input = new UnflattedArray($input);
79            $input->getUnflattenedArray();
80        }
81        $input = $input->getValue();
82        return $input;
83    }
84    /**
85     * Set Default values
86     *
87     * @return array
88     *
89     */
90    public function getDefaults()
91    {
92        return [];
93    }
94
95    /**
96     * This method is private, because the used library should not be used outside of this class!
97     *
98     */
99    public function getValidator(string $locale = 'de_DE', int $resolveLevel = 0): Validator
100    {
101        $jsonSchema = self::readJsonSchema()->withResolvedReferences($resolveLevel);
102        $data = (new Schema($this))->withoutRefs();
103        if (Property::__keyExists('$schema', $data)) {
104            unset($data['$schema']);
105        }
106        $validator = new Validator($data->toJsonObject(true), $jsonSchema, $locale);
107        return $validator;
108    }
109
110    /**
111     * Check if the given data validates against the given jsonSchema
112     *
113     * @return bool
114     */
115    public function isValid($resolveLevel = 0): bool
116    {
117        $validator = $this->getValidator('de_DE', $resolveLevel = 0);
118        return $validator->isValid();
119    }
120
121    /**
122     * Check if the given data validates against the given jsonSchema
123     *
124     * @throws \BO\Zmsentities\Exception\SchemaValidation
125     * @return bool
126     */
127    public function testValid($locale = 'de_DE', $resolveLevel = 0): bool
128    {
129        $validator = $this->getValidator($locale, $resolveLevel);
130        if (!$validator->isValid()) {
131            $exception = new \BO\Zmsentities\Exception\SchemaValidation();
132            $exception->setSchemaName($this->getEntityName());
133            $exception->setValidationError($validator->getErrors());
134            throw $exception;
135        }
136        return true;
137    }
138
139    /**
140     * create an example for testing
141     *
142     * @return self
143     */
144    public static function createExample()
145    {
146        $object = new static();
147        return $object->getExample();
148    }
149
150    /**
151     * return a new object as example
152     *
153     * @return static
154     */
155    public static function getExample()
156    {
157        $class = get_called_class();
158        $jsonSchema = self::readJsonSchema();
159        if ($jsonSchema->offsetExists('example')) {
160            return new $class($jsonSchema['example']);
161        }
162        return new $class();
163    }
164
165    protected static function readJsonSchema()
166    {
167        $class = get_called_class();
168        if (!array_key_exists($class, self::$schemaCache)) {
169            self::$schemaCache[$class] = Loader::asArray($class::$schema);
170        }
171        return self::$schemaCache[$class];
172    }
173
174    public function getEntityName(): string
175    {
176        $entity = get_class($this);
177        $entity = preg_replace('#.*[\\\]#', '', $entity);
178        $entity = strtolower($entity);
179        return $entity;
180    }
181
182    public function setJsonCompressLevel($jsonCompressLevel): static
183    {
184        $this->jsonCompressLevel = $jsonCompressLevel;
185        return $this;
186    }
187
188    #[\Override]
189    public function jsonSerialize(): mixed
190    {
191        $schema = array(
192            '$schema' => 'https://schema.berlin.de/queuemanagement/' . $this->getEntityName() . '.json'
193        );
194        $schema = array_merge($schema, $this->getArrayCopy());
195        if ($this instanceof \BO\Zmsentities\Helper\NoSanitize) {
196            $serialize = $schema;
197        } else {
198            $schema = new Schema($schema);
199            $schema->setDefaults($this->getDefaults());
200            $schema->setJsonCompressLevel($this->jsonCompressLevel);
201            $serialize = $schema->toJsonObject();
202        }
203        return $serialize;
204    }
205
206    public function __toString()
207    {
208        return json_encode($this->jsonSerialize(), JSON_HEX_QUOT);
209    }
210
211    public function __clone()
212    {
213        foreach ($this as $key => $property) {
214            if (is_object($property)) {
215                $this[$key] = clone $property;
216            }
217        }
218    }
219
220    /**
221     * Performs a merge with an iterable
222     * Sub-entities are preserved
223     */
224    public function addData(array|object $mergeData): static
225    {
226        foreach ($mergeData as $key => $item) {
227            if (isset($this[$key])) {
228                if ($this[$key] instanceof Entity) {
229                    $this[$key]->setResolveLevel($this->getResolveLevel() - 1);
230                    $this[$key]->addData($item);
231                } elseif ($this[$key] instanceof \BO\Zmsentities\Collection\Base) {
232                    $this[$key]->exchangeArray([]);
233                    $this[$key]->setResolveLevel($this->getResolveLevel() - 1);
234                    $this[$key]->addData($item);
235                } elseif (is_array($this[$key])) {
236                    $this[$key] = array_replace_recursive($this[$key], $item);
237                } else {
238                    $this[$key] = $item;
239                }
240            } else {
241                $this[$key] = $item;
242            }
243        }
244        return $this;
245    }
246
247    /**
248     * Performs addData on a cloned entity
249     */
250    public function withData($mergeData): static
251    {
252        $entity = clone $this;
253        $entity->addData($mergeData);
254        return $entity;
255    }
256
257    public function hasId(): bool
258    {
259        return (false !== $this->getId()) ? true : false;
260    }
261
262    public function getId()
263    {
264        $idName = $this::PRIMARY;
265        return ($this->offsetExists($idName) && $this[$idName]) ? $this[$idName] : false;
266    }
267
268    /**
269     * Allow accessing properties without checking if it exists first
270     *
271     * @return \BO\Zmsentities\Helper\Property
272     */
273    public function toProperty()
274    {
275        return new \BO\Zmsentities\Helper\Property($this);
276    }
277
278    public function hasProperty($propertyName)
279    {
280        return $this->toProperty()->{$propertyName}->isAvailable();
281    }
282
283    public function getProperty(string $propertyName, $default = '')
284    {
285        return $this->toProperty()->{$propertyName}->get($default);
286    }
287
288    /**
289     * Change property without changing original
290     */
291    public function withProperty($propertyName, $newValue): static
292    {
293        $entity = clone $this;
294        $entity[$propertyName] = $newValue;
295        return $entity;
296    }
297
298    /**
299     * Reduce data of dereferenced entities to a required minimum
300     *
301     * @return static
302     */
303    public function withLessData()
304    {
305        return clone $this;
306    }
307
308    /**
309     * Reduce data of dereferenced entities to a required minimum
310     *
311     * @return static
312     */
313    public function withCleanedUpFormData()
314    {
315        $entity = clone $this;
316        if (isset($entity['save'])) {
317            unset($entity['save']);
318        }
319        if (isset($entity['removeImage'])) {
320            unset($entity['removeImage']);
321        }
322        return $entity;
323    }
324
325    /**
326     * @return Int
327     */
328    public function getResolveLevel()
329    {
330        return $this->resolveLevel;
331    }
332
333    /**
334     * @param Int $resolveLevel
335     * @return self
336     */
337    public function setResolveLevel($resolveLevel)
338    {
339        $this->resolveLevel = $resolveLevel;
340        return $this;
341    }
342
343    /**
344     * Set a very strict resolveLevel to reduce data
345     *
346     * @param Int $resolveLevel
347     * @return self
348     */
349    public function withResolveLevel($resolveLevel)
350    {
351        if ($resolveLevel >= 0) {
352            $entity = clone $this;
353            foreach ($entity as $key => $value) {
354                if ($value instanceof Entity || $value instanceof \BO\Zmsentities\Collection\Base) {
355                    $entity[$key] = $value->withResolveLevel($resolveLevel - 1);
356                } else {
357                    $entity[$key] = $value;
358                }
359            }
360            $entity->setResolveLevel($resolveLevel);
361            return $entity;
362        } else {
363            return $this->withReference();
364        }
365    }
366
367    /**
368     * Replace data with a jsonSchema Reference
369     *
370     * @param Array $additionalData
371     * @return self
372     */
373    public function withReference($additionalData = [])
374    {
375        if (isset($this[$this::PRIMARY])) {
376            $additionalData['$ref'] =
377                $this::$schemaRefPrefix . $this->getEntityName() . '/' . $this[$this::PRIMARY] . '/';
378            return $additionalData;
379        } else {
380            return $this->withResolveLevel(0);
381        }
382    }
383}