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