Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
79.84% covered (warning)
79.84%
99 / 124
78.57% covered (warning)
78.57%
22 / 28
CRAP
0.00% covered (danger)
0.00%
0 / 1
Base
79.84% covered (warning)
79.84%
99 / 124
78.57% covered (warning)
78.57%
22 / 28
85.57
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFirst
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getLast
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 sortByName
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 sortByContactName
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 sortByCustomKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 sortByCustomStringKey
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 __clone
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 hasEntity
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 getEntity
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 addEntity
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 offsetSet
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 addData
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
5.01
 addList
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getIds
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getIdsCsv
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCsvForProperty
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getCsvForPropertyList
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 withValueFor
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 withLessData
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setJsonCompressLevel
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getAsArray
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 jsonSerialize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 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%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 chunk
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3/**
4 * @package Zmsdldb
5 * @copyright BerlinOnline Stadtportal GmbH & Co. KG
6 **/
7
8namespace BO\Zmsentities\Collection;
9
10use BO\Zmsentities\Helper\Sorter;
11use BO\Zmsentities\Schema\Entity;
12
13/**
14 * @SuppressWarnings(NumberOfChildren)
15 * @SuppressWarnings(Public)
16 * @SuppressWarnings(Complexity)
17 *
18 * @template T of Entity
19 * @extends \ArrayObject<int|string, T>
20 * @psalm-consistent-constructor
21 */
22class Base extends \ArrayObject implements \JsonSerializable
23{
24    /** @var string */
25    public const string ENTITY_CLASS = '';
26
27    /**
28     * @var int|null $resolveLevel indicator on data integrity
29     */
30    protected $resolveLevel = null;
31
32    public function __construct(object|array $array = [], int $flags = 0, string $iteratorClass = "ArrayIterator")
33    {
34        /** @var class-string<\ArrayIterator> $iteratorClass */
35        parent::__construct($array, $flags, $iteratorClass);
36    }
37
38    /**
39     * @return T|null
40     */
41    public function getFirst()
42    {
43        $item = $this->getIterator()->current();
44        return $item;
45    }
46
47    /**
48     * @return Entity|false
49     *
50     */
51    public function getLast()
52    {
53        $copy = $this->getArrayCopy();
54        $item = end($copy);
55        return $item;
56    }
57
58    /**
59     * @return static
60     *
61     */
62    public function sortByName()
63    {
64        $this->uasort(function ($a, $b) {
65            return strcmp(
66                Sorter::toSortableString(ucfirst($a->name ?? '')),
67                Sorter::toSortableString(ucfirst($b->name ?? ''))
68            );
69        });
70        return $this;
71    }
72
73    public function sortByContactName(): static
74    {
75        $this->uasort(function ($a, $b) {
76            return strcmp(
77                Sorter::toSortableString(ucfirst($a->contact['name'] ?? '')),
78                Sorter::toSortableString(ucfirst($b->contact['name'] ?? ''))
79            );
80        });
81        return $this;
82    }
83
84    public function sortByCustomKey(string $key): static
85    {
86        $this->uasort(function ($a, $b) use ($key): mixed {
87            return ($a[$key] - $b[$key]);
88        });
89        return $this;
90    }
91
92    public function sortByCustomStringKey(mixed $key): static
93    {
94        $this->uasort(function ($a, $b) use ($key) {
95            return strcmp(
96                Sorter::toSortableString(ucfirst($a[$key] ?? '')),
97                Sorter::toSortableString(ucfirst($b[$key] ?? ''))
98            );
99        });
100        return $this;
101    }
102
103    public function __clone()
104    {
105        foreach ($this as $key => $property) {
106            $this[$key] = clone $property;
107        }
108    }
109
110    public function hasEntity(mixed $primary): bool
111    {
112        foreach ($this as $entity) {
113            if (isset($entity->{$entity::PRIMARY}) && $primary == $entity->{$entity::PRIMARY}) {
114                return true;
115            }
116        }
117        return false;
118    }
119
120    /**
121     * @return Entity|null
122     *
123     */
124    public function getEntity(string|int $primary)
125    {
126        foreach ($this as $entity) {
127            if (isset($entity->{$entity::PRIMARY}) && $primary == $entity->{$entity::PRIMARY}) {
128                return $entity;
129            }
130        }
131        return null;
132    }
133
134    public function addEntity(\BO\Zmsentities\Schema\Entity $entity): static
135    {
136        /** @var T $entity */
137        $this->append($entity);
138        return $this;
139    }
140
141    /**
142     * @param int|string|null $index
143     * @param Entity|array $value
144     */
145    #[\Override]
146    public function offsetSet(mixed $index, mixed $value): void
147    {
148        $className = $this::ENTITY_CLASS;
149        if (is_a($value, $className)) {
150            /** @var T $entity */
151            $entity = $value;
152            /** @psalm-suppress PossiblyNullArgument */
153            parent::offsetSet($index, $entity);
154        } elseif (is_array($value)) {
155            /** @var T $entity */
156            $entity = new $className($value);
157            /** @psalm-suppress PossiblyNullArgument */
158            parent::offsetSet($index, $entity);
159        } else {
160            $given = get_debug_type($value);
161            throw new \Exception('Invalid entity ' . $given . ' for collection ' . __CLASS__);
162        }
163    }
164
165    public function addData(mixed $mergeData): static
166    {
167        foreach ($mergeData as $item) {
168            if ($item instanceof Entity) {
169                if (null === $item->getResolveLevel()) {
170                    $item->setResolveLevel($this->getResolveLevel());
171                }
172                $this->addEntity($item);
173            } else {
174                $className = $this::ENTITY_CLASS;
175                $entity = new $className($item);
176                if (!$entity instanceof Entity) {
177                    throw new \Exception('Invalid entity ' . get_debug_type($entity) . ' for collection ' . __CLASS__);
178                }
179                $entity->setResolveLevel($this->getResolveLevel());
180                $this->addEntity($entity);
181            }
182        }
183        return $this;
184    }
185
186    /**
187     * @param Base<T> $list
188     */
189    public function addList(Base $list): static
190    {
191        foreach ($list as $item) {
192            $this->addEntity($item);
193        }
194        return $this;
195    }
196
197    /**
198     * @return array
199     *
200     */
201    public function getIds()
202    {
203        $list = [];
204        foreach ($this as $entity) {
205            $list[] = $entity->id;
206        }
207        return $list;
208    }
209
210    public function getIdsCsv(): string
211    {
212        return implode(',', $this->getIds());
213    }
214
215    public function getCsvForProperty(mixed $propertyName, string $csvSeperator = ','): string
216    {
217        $list = [];
218        foreach ($this as $entry) {
219            if ($entry->hasProperty($propertyName)) {
220                $list[] = $entry->getProperty($propertyName);
221            }
222        }
223        return implode($csvSeperator, $list);
224    }
225
226    public function getCsvForPropertyList(array $propertyList, string $propertySeperator = '', string $csvSeperator = ','): string
227    {
228        $list = [];
229        foreach ($this as $entry) {
230            $propertyConcat = '';
231            foreach ($propertyList as $propertyName) {
232                if ($entry->hasProperty($propertyName)) {
233                    if ($propertyConcat) {
234                        $propertyConcat .= $propertySeperator;
235                    }
236                    $propertyConcat .= $entry->getProperty($propertyName);
237                }
238            }
239            if ($propertyConcat) {
240                $list[] = $propertyConcat;
241            }
242        }
243        return implode($csvSeperator, $list);
244    }
245
246    /**
247     * Change a parameter on all entries
248     *
249     */
250    public function withValueFor(mixed $param, mixed $newValue): static
251    {
252        /** @psalm-suppress UnsafeGenericInstantiation */
253        $list = new static();
254        foreach ($this as $entry) {
255            $list[] = $entry->withProperty($param, $newValue);
256        }
257        return $list;
258    }
259
260    /**
261     * Reduce items data of dereferenced entities to a required minimum
262     *
263     * @return static
264     *
265     */
266    public function withLessData(array $keepArray = [])
267    {
268        /** @psalm-suppress UnsafeGenericInstantiation */
269        $list = new static();
270        foreach ($this as $key => $item) {
271            /** @psalm-suppress TooManyArguments */
272            $list[$key] = $item->withLessData($keepArray);
273        }
274        return $list;
275    }
276
277    public function setJsonCompressLevel(mixed $jsonCompressLevel): void
278    {
279        foreach ($this as $item) {
280            $item->setJsonCompressLevel($jsonCompressLevel);
281        }
282    }
283
284    /**
285     * @return T[]
286     *
287     */
288    public function getAsArray(): array
289    {
290        $array = [];
291        foreach ($this as $item) {
292            $array[] = $item;
293        }
294
295        return $array;
296    }
297
298    #[\Override]
299    public function jsonSerialize(): mixed
300    {
301        return $this->getArrayCopy();
302    }
303
304    public function __toString()
305    {
306        $list = [];
307        foreach ($this as $item) {
308            $list[] = $item->__toString();
309        }
310        return "[" . implode(',', $list) . "]";
311    }
312
313    /**
314     * @return int|null
315     */
316    public function getResolveLevel()
317    {
318        return $this->resolveLevel;
319    }
320
321    /**
322     * @param int|null $resolveLevel
323     * @return self
324     */
325    public function setResolveLevel($resolveLevel)
326    {
327        $this->resolveLevel = $resolveLevel;
328        return $this;
329    }
330
331    /**
332     * @param Int $resolveLevel
333     * @return self
334     */
335    public function withResolveLevel($resolveLevel)
336    {
337        /** @psalm-suppress UnsafeGenericInstantiation */
338        $collection = new static();
339        foreach ($this as $entity) {
340            $reduced = $entity->withResolveLevel($resolveLevel);
341            $collection[] = $reduced;
342        }
343        return $collection;
344    }
345
346    /**
347     * @return static[]
348     *
349     */
350    public function chunk(mixed $length): array
351    {
352        /** @psalm-suppress UnsafeGenericInstantiation */
353        $chunks = [new static()];
354        $id = 0;
355        foreach ($this as $entry) {
356            $chunkIndex = (int) floor($id / $length);
357            if (! isset($chunks[$chunkIndex])) {
358                /** @psalm-suppress UnsafeGenericInstantiation */
359                $chunks[$chunkIndex] = new static();
360            }
361
362            $chunks[$chunkIndex][] = $entry;
363
364            $id++;
365        }
366
367        return $chunks;
368    }
369}