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