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