Lines 83.18% 287 / 345
Methods 78.18% 43 / 55
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 setWaitingTimePreferences 100.00% 5 / 5 100.00% 1 / 1 2
 setTransferedProcessList 100.00% 2 / 2 100.00% 1 / 1 1
 getProcessTimeAverage 100.00% 1 / 1 100.00% 1 / 1 1
 getWorkstationCount 100.00% 1 / 1 100.00% 1 / 1 2
 withEstimatedWaitingTime 85.96% 49 / 57 0.00% 0 / 1 16.71
 getSortPriority 83.33% 5 / 6 0.00% 0 / 1 4.07
 withWaitingTime 100.00% 6 / 6 100.00% 1 / 1 3
 withSortedArrival 100.00% 9 / 9 100.00% 1 / 1 1
 withSortedWaitingTime 100.00% 2 / 2 100.00% 1 / 1 1
 withAppointment 100.00% 5 / 5 100.00% 1 / 1 3
 withOutAppointment 100.00% 5 / 5 100.00% 1 / 1 3
 getEstimatedWaitingTime 71.42% 10 / 14 0.00% 0 / 1 2.09
 withFakeWaitingnumber 100.00% 11 / 11 100.00% 1 / 1 1
 getFakeOrLastWaitingnumber 100.00% 4 / 4 100.00% 1 / 1 2
 getQueueByNumber 100.00% 4 / 4 100.00% 1 / 1 3
 getNextProcess 100.00% 17 / 17 100.00% 1 / 1 7
 getQueuePositionByNumber 100.00% 5 / 5 100.00% 1 / 1 3
 getCountWithWaitingTime 84.61% 11 / 13 0.00% 0 / 1 7.18
 withStatus 100.00% 5 / 5 100.00% 1 / 1 4
 withoutStatus 100.00% 5 / 5 100.00% 1 / 1 4
 withShortNameDestinationHint 100.00% 7 / 7 100.00% 1 / 1 4
 toProcessList 100.00% 6 / 6 100.00% 1 / 1 3
 withoutDublicates 100.00% 8 / 8 100.00% 1 / 1 3
 getWaitingNumberList 100.00% 4 / 4 100.00% 1 / 1 2
 getWaitingNumberListCsv 100.00% 1 / 1 100.00% 1 / 1 1
 withSelectedProcessFirst 0.00% 0 / 7 0.00% 0 / 1 12
 sortByCallTime 0.00% 0 / 11 0.00% 0 / 1 20
 [BO\Zmsentities\Collection\Base] __construct 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] getFirst 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] getLast 100.00% 3 / 3 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] sortByName 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] sortByContactName 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] sortByCustomKey 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] sortByCustomStringKey 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] __clone 100.00% 2 / 2 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] hasEntity 100.00% 4 / 4 100.00% 1 / 1 4
 [BO\Zmsentities\Collection\Base] getEntity 100.00% 4 / 4 100.00% 1 / 1 4
 [BO\Zmsentities\Collection\Base] addEntity 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] offsetSet 100.00% 9 / 9 100.00% 1 / 1 3
 [BO\Zmsentities\Collection\Base] addData 91.66% 11 / 12 0.00% 0 / 1 5.01
 [BO\Zmsentities\Collection\Base] addList 100.00% 3 / 3 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] getIds 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] getIdsCsv 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] getCsvForProperty 0.00% 0 / 5 0.00% 0 / 1 12
 [BO\Zmsentities\Collection\Base] getCsvForPropertyList 100.00% 11 / 11 100.00% 1 / 1 6
 [BO\Zmsentities\Collection\Base] withValueFor 0.00% 0 / 4 0.00% 0 / 1 6
 [BO\Zmsentities\Collection\Base] withLessData 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] setJsonCompressLevel 0.00% 0 / 2 0.00% 0 / 1 6
 [BO\Zmsentities\Collection\Base] getAsArray 0.00% 0 / 4 0.00% 0 / 1 6
 [BO\Zmsentities\Collection\Base] jsonSerialize 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] __toString 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] getResolveLevel 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] setResolveLevel 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] withResolveLevel 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] chunk 0.00% 0 / 9 0.00% 0 / 1 12
11class QueueList extends Base implements \BO\Zmsentities\Helper\NoSanitize
12{
13    public const string ENTITY_CLASS = '\BO\Zmsentities\Queue';
14
15    public const int FAKE_WAITINGNUMBER = -1;
16
17    public const array STATUS_IGNORE = ['called', 'processing', 'missed', 'parked'];
18
19    public const array STATUS_APPEND = ['missed', 'parked'];
20
21    public const array STATUS_CALLED = ['called', 'processing'];
22
23    public const array STATUS_FAKE = ['fake'];
24
25    public const int DEFAULT_PRIORITY_WITHOUT_APPOINTMENT = 3;
26
27    public const int DEFAULT_PRIORITY_WITH_APPOINTMENT = 2;
28
29    protected mixed $processTimeAverage = null;
30
31    protected mixed $workstationCount = null;
32
33    protected bool $fromProcessList = false;
34
35    public function setWaitingTimePreferences(mixed $processTimeAverage, mixed $workstationCount): static
36    {
37        if ($processTimeAverage <= 0) {
38            throw new \Exception("QueueList::withEstimatedWaitingTime() requires processTimeAverage");
39        }
40        $this->processTimeAverage = $processTimeAverage;
41        $this->workstationCount = $workstationCount;
42        return $this;
43    }
44
45    public function setTransferedProcessList(bool $bool = true): static
46    {
47        $this->fromProcessList = $bool;
48        return $this;
49    }
50
51    public function getProcessTimeAverage(): mixed
52    {
53        return $this->processTimeAverage;
54    }
55
56    public function getWorkstationCount(): mixed
57    {
58        return $this->workstationCount ? $this->workstationCount : 1;
59    }
60
61    public function withEstimatedWaitingTime(
62        mixed $processTimeAverage,
63        mixed $workstationCount,
64        \DateTimeInterface $dateTime,
65        bool $createFake = true
66    ): \BO\Zmsentities\Collection\QueueList {
67        $this->setWaitingTimePreferences($processTimeAverage, $workstationCount);
68        $queueFull = $this->withWaitingTime($dateTime);
69        $queueWithWaitingTime = $queueFull->withStatus(self::STATUS_CALLED);
70        $queueAppend = $queueFull->withStatus(self::STATUS_APPEND);
71        $queueFull = $queueFull->withoutStatus(self::STATUS_IGNORE);
72        if ($createFake) {
73            $queueFull = $queueFull->withFakeWaitingnumber($dateTime);
74        }
75        $listWithAppointment = $queueFull->withAppointment()->withSortedArrival()->getArrayCopy();
76        $listNoAppointment = $queueFull->withOutAppointment()->withSortedArrival()->getArrayCopy();
77        $nextWithAppointment = array_shift($listWithAppointment);
78        $nextNoAppointment = array_shift($listNoAppointment);
79        $currentTime = $dateTime->getTimestamp() + 120;
80        $optimisticTime = $dateTime->getTimestamp();
81        $pessimisticTime = $dateTime->getTimestamp();
82
83        $waitingTime = 0;
84        $waitingTimeOpt = 0;
85        $timeSlot = ($workstationCount) ? $processTimeAverage * 60 / $workstationCount : $processTimeAverage * 60;
86        $timeSlotOptimistic = $timeSlot * 0.8;
87        $timeSlotPessimistic = $timeSlot * 1.0;
88        $workstationSkip = ceil($workstationCount * 1.0);
89        $pessimisticTime += $timeSlot;
90        $waitingTimePes = (int)floor(($pessimisticTime - $dateTime->getTimestamp()) / 60);
91        while ($nextWithAppointment || $nextNoAppointment) {
92            if ($nextNoAppointment && (int) $nextNoAppointment->priority === 1) {
93                $nextNoAppointment->waitingTimeEstimate = $waitingTimePes;
94                $nextNoAppointment->waitingTimeOptimistic = $waitingTimeOpt;
95                $queueWithWaitingTime->addEntity($nextNoAppointment);
96                $nextNoAppointment = array_shift($listNoAppointment);
97            } elseif (
98                $nextNoAppointment
99                && $nextWithAppointment
100                && (int) $nextNoAppointment->priority === 2
101                && $nextNoAppointment->arrivalTime < $nextWithAppointment->arrivalTime
102            ) {
103                $nextNoAppointment->waitingTimeEstimate = $waitingTimePes;
104                $nextNoAppointment->waitingTimeOptimistic = $waitingTimeOpt;
105                $queueWithWaitingTime->addEntity($nextNoAppointment);
106                $nextNoAppointment = array_shift($listNoAppointment);
107            } elseif ($nextWithAppointment && $currentTime >= $nextWithAppointment->arrivalTime) {
108                $nextWithAppointment->waitingTimeEstimate = $waitingTime + 1;
109                $nextWithAppointment->waitingTimeOptimistic =
110                    floor(($nextWithAppointment->arrivalTime - $dateTime->getTimestamp()) / 60);
111                if ($optimisticTime >= $nextWithAppointment->arrivalTime) {
112                    $nextWithAppointment->waitingTimeOptimistic = $waitingTimeOpt;
113                    $nextWithAppointment->waitingTimeEstimate = $waitingTimePes;
114                }
115                $queueWithWaitingTime->addEntity($nextWithAppointment);
116                $nextWithAppointment = array_shift($listWithAppointment);
117            } elseif ($nextNoAppointment) {
118                $nextNoAppointment->waitingTimeEstimate = $waitingTimePes;
119                $nextNoAppointment->waitingTimeOptimistic = $waitingTimeOpt;
120                $queueWithWaitingTime->addEntity($nextNoAppointment);
121                $nextNoAppointment = array_shift($listNoAppointment);
122            }
123            $optimisticTime += (--$workstationSkip > 0) ? 0 : $timeSlotOptimistic;
124            $pessimisticTime += $timeSlotPessimistic;
125            $currentTime += $timeSlot;
126            $waitingTime = (int)ceil(($currentTime - $dateTime->getTimestamp()) / 60);
127            $waitingTimeOpt = (int)floor(($optimisticTime - $dateTime->getTimestamp()) / 60);
128            $waitingTimePes = (int)floor(($pessimisticTime - $dateTime->getTimestamp()) / 60);
129        }
130        $queueWithWaitingTime->addList($queueAppend);
131        return $queueWithWaitingTime;
132    }
133
134    private function getSortPriority(\BO\Zmsentities\Queue $queue): int
135    {
136        $priority = self::DEFAULT_PRIORITY_WITHOUT_APPOINTMENT;
137        if (empty($queue['priority']) && $queue['withAppointment']) {
138            $priority = self::DEFAULT_PRIORITY_WITH_APPOINTMENT;
139        }
140        if (!empty($queue['priority'])) {
141            $priority = (int) $queue['priority'];
142        }
143
144        return $priority;
145    }
146
147    public function withWaitingTime(\DateTimeInterface $dateTime): static
148    {
149        $queueList = $this;
150        $timestamp = $dateTime->getTimestamp();
151        foreach ($queueList as $entity) {
152            if ($timestamp > $entity->arrivalTime) {
153                $entity->waitingTime = floor(($timestamp - $entity->arrivalTime) / 60);
154            }
155        }
156        return $queueList;
157    }
158
159    public function withSortedArrival(): static
160    {
161        $queueList = $this;
162        $queueList->uasort(function ($first, $second) {
163            $firstPriority = $this->getSortPriority($first);
164            $secondPriority = $this->getSortPriority($second);
165
166            $firstSort = sprintf("%01d%011d%011d", $firstPriority, $first['arrivalTime'], $first['number']);
167            $secondSort = sprintf("%01d%011d%011d", $secondPriority, $second['arrivalTime'], $second['number']);
168
169            return strcmp($firstSort, $secondSort);
170        });
171        return $queueList;
172    }
173
174    public function withSortedWaitingTime(): \BO\Zmsentities\Collection\Base
175    {
176        $queueList = $this;
177        return $queueList->sortByCustomKey('waitingTimeEstimate');
178    }
179
180    public function withAppointment(): self
181    {
182        $queueList = new self();
183        foreach ($this as $entity) {
184            if ($entity->withAppointment) {
185                $queueList->addEntity($entity);
186            }
187        }
188        return $queueList;
189    }
190
191    public function withOutAppointment(): self
192    {
193        $queueList = new self();
194        foreach ($this as $entity) {
195            if (! $entity->withAppointment) {
196                $queueList->addEntity($entity);
197            }
198        }
199        return $queueList;
200    }
201
202    public function getEstimatedWaitingTime(mixed $processTimeAverage, mixed $workstationCount, \DateTimeInterface $dateTime): array
203    {
204        $queueList = $this->withFakeWaitingnumber($dateTime);
205        $queueList = $queueList
206          ->withEstimatedWaitingTime($processTimeAverage, $workstationCount, $dateTime);
207        $newEntity = $queueList->getFakeOrLastWaitingnumber();
208        if (!$newEntity instanceof \BO\Zmsentities\Queue) {
209            return [
210                'amountBefore' => 0,
211                'waitingTimeEstimate' => 0,
212            ];
213        }
214        $dataOfFackedEntity = array(
215            'amountBefore' => $queueList->getQueuePositionByNumber($newEntity->number),
216            'waitingTimeEstimate' => $newEntity->waitingTimeEstimate
217        );
218        return $dataOfFackedEntity;
219    }
220
221    public function withFakeWaitingnumber(\DateTimeInterface $dateTime): static
222    {
223        $queueList = $this;
224        $process = new \BO\Zmsentities\Process(['status' => 'deleted']);
225        $entity = (new \BO\Zmsentities\Queue())->setProcess($process);
226        $entity->number = self::FAKE_WAITINGNUMBER;
227        $entity->status = 'fake';
228        $entity->withAppointment = false;
229        $entity->destination = (string)$this->getProcessTimeAverage();
230        $entity->destinationHint = (string)$this->getWorkstationCount();
231        $entity->arrivalTime = $dateTime->getTimestamp();
232        $queueList->addEntity($entity);
233        return $queueList;
234    }
235
236    public function getFakeOrLastWaitingnumber(): \BO\Zmsentities\Schema\Entity|false
237    {
238        $entity = $this->getQueueByNumber(self::FAKE_WAITINGNUMBER);
239        if (!$entity) {
240            $entity = $this->getLast();
241        }
242
243        return $entity;
244    }
245
246    public function getQueueByNumber(int $number): \BO\Zmsentities\Queue|null
247    {
248        foreach ($this as $entity) {
249            if ($entity->number == $number) {
250                return $entity;
251            }
252        }
253        return null;
254    }
255
256    public function getNextProcess(\DateTimeInterface $dateTime, mixed $exclude = null): mixed
257    {
258        if (is_array($exclude)) {
259            $excludeNumbers = $exclude;
260        } else {
261            $excludeNumbers = explode(',', $exclude === null ? '' : (string) $exclude);
262        }
263        $queueList = clone $this;
264        // sort by waiting time to get realistic next process
265        $queueList = $queueList
266            ->withStatus(['confirmed', 'queued'])
267            ->withEstimatedWaitingTime(10, 1, $dateTime, false)
268            ->getArrayCopy()
269            ;
270        $next = array_shift($queueList);
271        $currentTime = $dateTime->getTimestamp();
272        while ($next) {
273            if (
274                ! in_array($next->number, $excludeNumbers) &&
275                (0 == $next->lastCallTime || ($next->lastCallTime + (5 * 60)) <= $currentTime)
276            ) {
277                return $next->getProcess();
278            }
279            $next = array_shift($queueList);
280        }
281        return null;
282    }
283
284    public function getQueuePositionByNumber(mixed $number): int|null
285    {
286        $list = array_values($this->getArrayCopy());
287        foreach ($list as $key => $entity) {
288            if ($entity->number == $number) {
289                return $key;
290            }
291        }
292        return null;
293    }
294
295    public function getCountWithWaitingTime(?\DateTimeInterface $dateTime = null): self
296    {
297        $queueList = new self();
298        $timestamp = $dateTime ? $dateTime->getTimestamp() : null;
299
300        foreach ($this as $entity) {
301            if (! $entity->withAppointment) {
302                $queueList->addEntity($entity);
303                continue;
304            }
305
306            if ($timestamp !== null) {
307                if ($entity->arrivalTime <= $timestamp) {
308                    $queueList->addEntity($entity);
309                }
310
311                continue;
312            }
313
314            if ($entity->waitingTime) {
315                $queueList->addEntity($entity);
316            }
317        }
318        return $queueList;
319    }
320
321    /**
322     * @param array $statusList of possible strings in process.status
323     */
324    public function withStatus(array $statusList): self
325    {
326        $queueList = new self();
327        foreach ($this as $entity) {
328            if ($entity->toProperty()->status->isAvailable() && in_array($entity->status, $statusList)) {
329                $queueList->addEntity($entity);
330            }
331        }
332        return $queueList;
333    }
334
335    /**
336     * @param array $statusList of excepted strings in process.status
337     */
338    public function withoutStatus(array $statusList): self
339    {
340        $queueList = new self();
341        foreach ($this as $entity) {
342            if ($entity->toProperty()->status->isAvailable() && ! in_array($entity->status, $statusList)) {
343                $queueList->addEntity($entity);
344            }
345        }
346        return $queueList;
347    }
348
349    public function withShortNameDestinationHint(\BO\Zmsentities\Cluster $cluster, \BO\Zmsentities\Scope $scope): self
350    {
351        $queueList = $this;
352        $list = new self();
353        foreach ($queueList as $entity) {
354            if ($cluster->shortNameEnabled && $scope->shortName) {
355                $entity->destinationHint = $scope->shortName;
356            }
357            $list->addEntity($entity);
358        }
359        return $list;
360    }
361
362    public function toProcessList(): ProcessList
363    {
364        $processList = new ProcessList();
365        foreach ($this as $queue) {
366            $process = $queue->getProcess();
367            if ($process) {
368                $processList->addEntity($process);
369            }
370        }
371        return $processList;
372    }
373
374    public function withoutDublicates(): self
375    {
376        $list = new self();
377        $exists = [];
378        foreach ($this as $entity) {
379            $key = "$entity->number-$entity->arrivalTime-$entity->withAppointment";
380            if (!isset($exists[$key])) {
381                $list[] = $entity;
382                $exists[$key] = true;
383            }
384        }
385        return $list; // Cloning with this function
386    }
387
388    public function getWaitingNumberList(): array
389    {
390        $list = [];
391        foreach ($this as $entity) {
392            $list[] = $entity->number;
393        }
394        return $list;
395    }
396
397    public function getWaitingNumberListCsv(): string
398    {
399        return implode(',', $this->getWaitingNumberList());
400    }
401
402    public function withSelectedProcessFirst(\BO\Zmsentities\Process $process): self
403    {
404        $queueList = $this;
405        $list = new self();
406        $list->addEntity($process->queue);
407        foreach ($queueList as $entity) {
408            if ($entity->number != $process->queue->number) {
409                $list->addEntity($entity);
410            }
411        }
412        return $list;
413    }
414
415    public function sortByCallTime(string $order): self
416    {
417        $queueListArray = [];
418        foreach ($this as $entity) {
419            $queueListArray[] = $entity;
420        }
421        usort($queueListArray, function ($a, $b) use ($order) {
422            if ($order === 'ascending') {
423                return $a->callTime <=> $b->callTime;
424            } elseif ($order === 'descending') {
425                return $b->callTime <=> $a->callTime;
426            }
427            throw new \InvalidArgumentException("Invalid sort order: $order. Use 'ascending' or 'descending'.");
428        });
429        return new self($queueListArray);
430    }
431}

Inherited from BO\Zmsentities\Collection\Base

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    }
41    public function getFirst()
42    {
43        $item = $this->getIterator()->current();
44        return $item;
45    }
51    public function getLast()
52    {
53        $copy = $this->getArrayCopy();
54        $item = end($copy);
55        return $item;
56    }
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    }
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    }
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    }
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    }
103    public function __clone()
104    {
105        foreach ($this as $key => $property) {
106            $this[$key] = clone $property;
107        }
108    }
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    }
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    }
134    public function addEntity(\BO\Zmsentities\Schema\Entity $entity): static
135    {
136        /** @var T $entity */
137        $this->append($entity);
138        return $this;
139    }
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    }
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    }
189    public function addList(Base $list): static
190    {
191        foreach ($list as $item) {
192            $this->addEntity($item);
193        }
194        return $this;
195    }
201    public function getIds()
202    {
203        $list = [];
204        foreach ($this as $entity) {
205            $list[] = $entity->id;
206        }
207        return $list;
208    }
210    public function getIdsCsv(): string
211    {
212        return implode(',', $this->getIds());
213    }
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    }
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    }
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    }
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    }
277    public function setJsonCompressLevel(mixed $jsonCompressLevel): void
278    {
279        foreach ($this as $item) {
280            $item->setJsonCompressLevel($jsonCompressLevel);
281        }
282    }
288    public function getAsArray(): array
289    {
290        $array = [];
291        foreach ($this as $item) {
292            $array[] = $item;
293        }
294
295        return $array;
296    }
299    public function jsonSerialize(): mixed
300    {
301        return $this->getArrayCopy();
302    }
304    public function __toString()
305    {
306        $list = [];
307        foreach ($this as $item) {
308            $list[] = $item->__toString();
309        }
310        return "[" . implode(',', $list) . "]";
311    }
316    public function getResolveLevel()
317    {
318        return $this->resolveLevel;
319    }
325    public function setResolveLevel($resolveLevel)
326    {
327        $this->resolveLevel = $resolveLevel;
328        return $this;
329    }
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    }
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    }