Lines 90.11% 565 / 627
Methods 82.08% 55 / 67
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 writeNewFromTicketprinter 100.00% 9 / 9 100.00% 1 / 1 1
 writeNewFromAdmin 100.00% 8 / 8 100.00% 1 / 1 2
 readByQueueNumberAndScope 100.00% 10 / 10 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readEntity 100.00% 11 / 11 100.00% 1 / 1 4
 [BO\Zmsbackend\Process\Service\Process] readById 100.00% 5 / 5 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readResolvedReferences 100.00% 7 / 7 100.00% 1 / 1 3
 [BO\Zmsbackend\Process\Service\Process] updateEntity 100.00% 16 / 16 100.00% 1 / 1 4
 [BO\Zmsbackend\Process\Service\Process] updateEntityDisplayNumber 0.00% 0 / 9 0.00% 0 / 1 6
 [BO\Zmsbackend\Process\Service\Process] updateEntityWithSlots 84.21% 16 / 19 0.00% 0 / 1 6.14
 [BO\Zmsbackend\Process\Service\Process] redirectToScope 0.00% 0 / 15 0.00% 0 / 1 2
 [BO\Zmsbackend\Process\Service\Process] readSlotCount 100.00% 11 / 11 100.00% 1 / 1 4
 [BO\Zmsbackend\Process\Service\Process] writeEntityWithNewAppointment 100.00% 18 / 18 100.00% 1 / 1 2
 [BO\Zmsbackend\Process\Service\Process] updateFollowingProcesses 100.00% 13 / 13 100.00% 1 / 1 4
 [BO\Zmsbackend\Process\Service\Process] updateReassignedRequests 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] writeNewProcess 100.00% 22 / 22 100.00% 1 / 1 7
 [BO\Zmsbackend\Process\Service\Process] readNewProcessId 100.00% 3 / 3 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readAuthKeyByProcessId 100.00% 9 / 9 100.00% 1 / 1 2
 [BO\Zmsbackend\Process\Service\Process] readList 100.00% 7 / 7 100.00% 1 / 1 2
 [BO\Zmsbackend\Process\Service\Process] readProcessesWithoutRequests 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsbackend\Process\Service\Process] attachAllRequestsInOneQuery 78.57% 11 / 14 0.00% 0 / 1 7.48
 [BO\Zmsbackend\Process\Service\Process] readEntityList 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readByWorkstation 100.00% 7 / 7 100.00% 1 / 1 2
 [BO\Zmsbackend\Process\Service\Process] readProcessListByScopesAndTime 100.00% 11 / 11 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readConflictListByScopeAndTime 100.00% 24 / 24 100.00% 1 / 1 4
 [BO\Zmsbackend\Process\Service\Process] readProcessListByScopeAndStatus 100.00% 9 / 9 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readProcessListByClusterAndTime 100.00% 6 / 6 100.00% 1 / 1 3
 [BO\Zmsbackend\Process\Service\Process] readProcessListCountByScope 100.00% 9 / 9 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readProcessListByMailAddress 100.00% 11 / 11 100.00% 1 / 1 2
 [BO\Zmsbackend\Process\Service\Process] readListByMailAndStatusList 100.00% 10 / 10 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] updateProcessStatus 80.00% 4 / 5 0.00% 0 / 1 2.03
 [BO\Zmsbackend\Process\Service\Process] shouldUpdateDisplayNumber 62.50% 5 / 8 0.00% 0 / 1 6.32
 [BO\Zmsbackend\Process\Service\Process] writeDeletedEntity 100.00% 15 / 15 100.00% 1 / 1 4
 [BO\Zmsbackend\Process\Service\Process] deleteEntity 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] writeCanceledEntity 100.00% 12 / 12 100.00% 1 / 1 2
 [BO\Zmsbackend\Process\Service\Process] writeBlockedEntity 100.00% 24 / 24 100.00% 1 / 1 6
 [BO\Zmsbackend\Process\Service\Process] writeRequestsToDb 100.00% 13 / 13 100.00% 1 / 1 7
 [BO\Zmsbackend\Process\Service\Process] deleteRequestsForProcessId 100.00% 6 / 6 100.00% 1 / 1 2
 [BO\Zmsbackend\Process\Service\Process] readExpiredProcessList 100.00% 9 / 9 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readUnconfirmedProcessList 0.00% 0 / 11 0.00% 0 / 1 2
 [BO\Zmsbackend\Process\Service\Process] readExpiredProcessListByStatus 100.00% 10 / 10 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readExpiredReservationsList 100.00% 11 / 11 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readEmailReminderProcessListByInterval 100.00% 11 / 11 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] readDeallocateProcessList 100.00% 9 / 9 100.00% 1 / 1 1
 [BO\Zmsbackend\Process\Service\Process] isAppointmentAllowedWithSameMail 94.44% 17 / 18 0.00% 0 / 1 10.02
 [BO\Zmsbackend\Process\Service\Process] isMailWhitelisted 90.00% 9 / 10 0.00% 0 / 1 6.04
 [BO\Zmsbackend\Process\Service\Process] readProcessWithSameDayAndDisplayNumber 0.00% 0 / 9 0.00% 0 / 1 2
 [BO\Zmsbackend\Process\Service\Process] readProcessListByExternalUserId 100.00% 14 / 14 100.00% 1 / 1 3
 [BO\Zmsbackend\Process\Service\Process] readAssignedWorkstationIdForUpdate 80.00% 4 / 5 0.00% 0 / 1 3.07
 [BO\Zmsbackend\Base] __construct 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] init 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] getWriter 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsbackend\Base] getReader 100.00% 5 / 5 100.00% 1 / 1 3
 [BO\Zmsbackend\Base] fetchPreparedStatement 100.00% 9 / 9 100.00% 1 / 1 3
 [BO\Zmsbackend\Base] startExecute 100.00% 5 / 5 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] pdoExceptionHandler 100.00% 13 / 13 100.00% 1 / 1 4
 [BO\Zmsbackend\Base] fetchStatement 100.00% 3 / 3 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchOne 100.00% 7 / 7 100.00% 1 / 1 2
 [BO\Zmsbackend\Base] fetchRow 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchValue 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchAll 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchHandle 100.00% 5 / 5 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchList 100.00% 8 / 8 100.00% 1 / 1 2
 [BO\Zmsbackend\Base] writeItem 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] deleteItem 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] perform 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchAffected 100.00% 8 / 8 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] hashStringValue 50.00% 5 / 10 0.00% 0 / 1 8.12
8class ProcessStatusQueued extends Process
9{
10    public function writeNewFromTicketprinter(
11        \BO\Zmsentities\Scope $scope,
12        \DateTimeInterface $dateTime,
13        array $requestIds = []
14    ): Entity {
15        $process = Entity::createFromScope($scope, $dateTime);
16        $process->setStatus('queued');
17        $process->isTicketprinter = true;
18        $newQueueNumber = (new \BO\Zmsbackend\Scope\Service\Scope())->readWaitingNumberUpdated($scope->id, $dateTime);
19        $process->addQueue($newQueueNumber, $dateTime);
20        $process = $this->writeNewProcess($process, $dateTime);
21
22        $process->updateRequests($scope->getSource(), implode(',', $requestIds));
23        $this->writeRequestsToDb($process);
24
25        return $process;
26    }
27
28    public function writeNewFromAdmin(Entity $process, \DateTimeInterface $dateTime): Entity|null
29    {
30        $process->setStatus('queued');
31        $process->getFirstAppointment()->date = $dateTime->modify('00:00:00')->getTimestamp();
32        $newQueueNumber = (new \BO\Zmsbackend\Scope\Service\Scope())->readWaitingNumberUpdated($process->scope['id'], $dateTime, false);
33        $process->addQueue($newQueueNumber, $dateTime);
34        $process = $this->writeNewProcess($process, $dateTime);
35        if (0 < $process->getRequests()->count()) {
36            $this->writeRequestsToDb($process);
37        }
38        return $this->readEntity($process->id, $process->authKey, 2);
39    }
40
41    /**
42     * Read process by queue number and scopeId
43     *
44     * @param
45     * scopeId
46     *
47     * @return string authKey
48     */
49    public function readByQueueNumberAndScope(int $queueNumber, $scopeId, int $resolveReferences = 0, int $queueLimit = 10000)
50    {
51        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
52        $query
53            ->addEntityMapping()
54            ->addResolvedReferences($resolveReferences)
55            ->addConditionScopeId($scopeId)
56            ->addConditionAssigned()
57            ->addConditionQueueNumber($queueNumber, $queueLimit);
58        $process = $this->fetchOne($query, new Entity());
59        $process = $this->readResolvedReferences($process, $resolveReferences);
60        return $process;
61    }
62}

Inherited from BO\Zmsbackend\Process\Service\Process

24    public function readEntity(int|string|null $processId = null, string|\BO\Zmsbackend\Helper\NoAuth|null $authKey = null, int $resolveReferences = 2): ?Entity
25    {
26        if (null === $processId || null === $authKey) {
27            return null;
28        }
29        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
30        $query->addEntityMapping()
31            ->addResolvedReferences($resolveReferences)
32            ->addConditionProcessId($processId);
33        if (!$authKey instanceof \BO\Zmsbackend\Helper\NoAuth) {
34            $query->addConditionAuthKey($authKey);
35        }
36        $process = $this->fetchOne($query, new Entity());
37        $process = $this->readResolvedReferences($process, $resolveReferences);
38
39        return $process;
40    }
42    public function readById($processId, int $resolveReferences = 1): Entity
43    {
44        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
45        $query->addEntityMapping()
46            ->addResolvedReferences($resolveReferences)
47            ->addConditionProcessId($processId);
48        return $this->fetchOne($query, new Entity());
49    }
56    public function readResolvedReferences(\BO\Zmsentities\Schema\Entity $entity, int $resolveReferences)
57    {
58        if (0 <= $resolveReferences) {
59            if ($entity->archiveId) {
60                $entity->requests = (new \BO\Zmsbackend\Request\Service\Request())
61                    ->readRequestByArchiveId($entity->archiveId, $resolveReferences - 1);
62            } else {
63                $entity->requests = (new \BO\Zmsbackend\Request\Service\Request())
64                    ->readRequestByProcessId($entity->id, $resolveReferences - 1);
65            }
66        }
67        return $entity;
68    }
77    public function updateEntity(\BO\Zmsentities\Process $process, \DateTimeInterface $now, int $resolveReferences = 0, string|null $previousStatus = null, ?\BO\Zmsentities\Useraccount $useraccount = null): Entity
78    {
79        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::UPDATE);
80        $query->addConditionProcessId($process->getId());
81        $query->addConditionAuthKey($process->getAuthKey());
82        $query->addValuesUpdateProcess($process, $now, 0, $previousStatus);
83        if ($this->perform($query->getLockProcessId(), ['processId' => $process->getId()])) {
84            $this->writeItem($query);
85            $this->writeRequestsToDb($process);
86        }
87        $process = $this->readEntity($process->getId(), $process->authKey, $resolveReferences);
88        if (!$process->getId()) {
89            throw new \BO\Zmsbackend\Process\Exception\ProcessUpdateFailed();
90        }
91        $this->perform(\BO\Zmsbackend\Process\Repository\Process::QUERY_UPDATE_FOLLOWING_PROCESS, [
92            'reserved' => ($process->status == 'reserved') ? 1 : 0,
93            'processID' => $process->getId(),
94        ]);
95        \BO\Zmsbackend\Log\Service\Log::writeProcessLog("UPDATE (\BO\Zmsbackend\Process\Service\Process::updateEntity) $process ", \BO\Zmsbackend\Log\Service\Log::ACTION_EDITED, $process, $useraccount);
96        return $process;
97    }
99    public function updateEntityDisplayNumber(Entity $process): Entity
100    {
101        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::UPDATE);
102        $query->addConditionProcessId($process->getId());
103        $query->addConditionAuthKey($process->getAuthKey());
104
105        $process->displayNumber = $query->getNewDisplayNumber($process);
106        $query->addValueDisplayNumber($process);
107        if ($this->perform($query->getLockProcessId(), ['processId' => $process->getId()])) {
108            $this->writeItem($query);
109        }
110
111        \BO\Zmsbackend\Log\Service\Log::writeProcessLog("UPDATE (updateEntityDisplayNumber) $process ", \BO\Zmsbackend\Log\Service\Log::ACTION_EDITED, $process);
112        return $process;
113    }
125    public function updateEntityWithSlots(\BO\Zmsentities\Process $process, \DateTimeInterface $now, $slotType = "intern", $slotsRequired = 0, int $resolveReferences = 0, $userAccount = null)
126    {
127        if ('intern' != $slotType) {
128            return $this->updateEntity($process, $now, $resolveReferences, null, $userAccount);
129        }
130        $process = clone $process;
131        $appointment = $process->getAppointments()->getFirst();
132        $slotList = (new \BO\Zmsbackend\Slot\Service\Slot())->readByAppointment($appointment, $slotsRequired, true);
133        $processEntityList = $this->readEntityList($process->getId());
134        foreach ($processEntityList as $entity) {
135            if ($process->getId() != $entity->getId()) {
136                $this->writeDeletedEntity($entity->getId());
137            }
138        }
139
140        foreach ($slotList as $slot) {
141            $newProcess = clone $process;
142            $newProcess->getFirstAppointment()->setTime($slot->time);
143            if (! $newProcess->getFirstAppointment()->isMatching($process->getFirstAppointment())) {
144                $this->writeNewProcess($newProcess, $now, $process->id, 0, true, $userAccount);
145            }
146        }
147
148        $appointment->addSlotCount($slotList->count());
149
150        $processEntity = $this->updateEntity($process, $now, $resolveReferences, null, $userAccount);
151        $process = $this->readEntity($process->getId(), $process->authKey, $resolveReferences);
152
153        \BO\Zmsbackend\Log\Service\Log::writeProcessLog("CREATE (\BO\Zmsbackend\Process\Service\Process::updateEntityWithSlots) $process ", \BO\Zmsbackend\Log\Service\Log::ACTION_EDITED, $process, $userAccount);
154        return $processEntity;
155    }
157    public function redirectToScope(Entity $process, \BO\Zmsentities\Scope $scope, int $waitingNumber, ?\BO\Zmsentities\Useraccount $useraccount = null): Entity
158    {
159        $datetime = \App::$now;
160        $process->setStatus('confirmed');
161        $appointment = $process->getFirstAppointment();
162        $date = (new \DateTimeImmutable())->setTimestamp($appointment->date);
163        $date = $date->setTime(0, 0, 0);
164        $appointment->date = $date->getTimestamp();
165        $process->appointments = new AppointmentList([$appointment]);
166        $newQueueNumber = (new \BO\Zmsbackend\Scope\Service\Scope())->readWaitingNumberUpdated($scope->id, $datetime);
167        $process->addQueue($newQueueNumber, $datetime);
168        $process->queue['number'] = $waitingNumber;
169        $process->priority = \BO\Zmsentities\Collection\QueueList::DEFAULT_PRIORITY_WITH_APPOINTMENT;
170        $process = $this->writeNewProcess($process, $datetime);
171        \BO\Zmsbackend\Log\Service\Log::writeProcessLog("CREATE (\BO\Zmsbackend\Process\Service\Process::redirectToScope) $process ", \BO\Zmsbackend\Log\Service\Log::ACTION_REDIRECTED, $process, $useraccount);
172        $this->writeRequestsToDb($process);
173        return $process;
174    }
176    public function readSlotCount(\BO\Zmsentities\Process $process): Entity
177    {
178        $scope = new \BO\Zmsentities\Scope($process->scope);
179        $requestRelationList = (new \BO\Zmsbackend\RequestRelation\Service\RequestRelation())
180            ->readListByProviderId($scope->getProviderId(), $scope->getSource());
181        $appointment = $process->getAppointments()->getFirst();
182        $appointment->slotCount = 0;
183        foreach ($process->requests as $request) {
184            foreach ($requestRelationList as $requestRelation) {
185                if ($requestRelation->getRequest()->getId() == $request->getId()) {
186                    $appointment->slotCount += $requestRelation->getSlotCount();
187                }
188            }
189        }
190        $appointment->slotCount = round($appointment->slotCount, 0);
191        return $process;
192    }
197    public function writeEntityWithNewAppointment(\BO\Zmsentities\Process $process, \BO\Zmsentities\Appointment $appointment, \DateTimeInterface $now, $slotType = 'public', int $slotsRequired = 0, int $resolveReferences = 0, bool $keepReserved = false): Entity|null
198    {
199        // clone to new process with id = 0 and new appointment to reserve
200        $processNew = clone $process;
201        $processNew->id = 0;
202        $processNew->scope = $appointment->scope;
203        $processNew->queue['arrivalTime'] = 0;
204        $processNew->queue['number'] = 0;
205        $processNew->appointments = (new \BO\Zmsentities\Collection\AppointmentList())->addEntity($appointment);
206//delete old process with following processes
207        $this->writeDeletedEntity($process->getId());
208//reserve new appointment
209        $processNew = \BO\Zmsbackend\Process\Service\ProcessStatusFree::init()
210            ->writeEntityReserved($processNew, $now, $slotType, $slotsRequired);
211        $processTempNewId = $processNew->getId();
212// reassign credentials of new process with credentials of old process
213        $processNew->withReassignedCredentials($process);
214// update new process with old credentials, also assigned requests and following slots
215        $this->updateFollowingProcesses($processTempNewId, $processNew);
216        $this->updateReassignedRequests($processTempNewId, $processNew->getId());
217//delete slot mapping for temp process id
218        (new \BO\Zmsbackend\Slot\Service\Slot())->deleteSlotProcessMappingFor($processTempNewId);
219//write new slot mapping for changed process with old credentials because new appointment data
220        (new \BO\Zmsbackend\Slot\Service\Slot())->writeSlotProcessMappingFor($processNew->getId());
221        \BO\Zmsbackend\Log\Service\Log::writeProcessLog("UPDATE (\BO\Zmsbackend\Process\Service\Process::writeEntityWithNewAppointment) $process ", \BO\Zmsbackend\Log\Service\Log::ACTION_EDITED, $process);
222        $status = ($keepReserved) ? Entity::STATUS_RESERVED : ENTITY::STATUS_CONFIRMED;
223        return $this->updateProcessStatus($processNew, $status, $now, $resolveReferences);
224    }
229    public function updateFollowingProcesses($processId, \BO\Zmsentities\Process $processData): void
230    {
231        $this->perform(\BO\Zmsbackend\Process\Repository\Process::QUERY_REASSIGN_PROCESS_CREDENTIALS, [
232            'newProcessId' => $processData->getId(),
233            'newAuthKey' => $processData->getAuthKey(),
234            'processId' => $processId
235        ]);
236        $processEntityList = $this->readEntityList($processId);
237        if ($processEntityList->count()) {
238            foreach ($processEntityList as $entity) {
239                if ($entity->getId() != $processId) {
240                    $this->perform(\BO\Zmsbackend\Process\Repository\Process::QUERY_REASSIGN_FOLLWING_PROCESS, [
241                        'newProcessId' => $processData->getId(),
242                        'processId' => $processId
243                    ]);
244                }
245            }
246        }
247    }
252    public function updateReassignedRequests($processId, $newProcessId): void
253    {
254        $this->perform(\BO\Zmsbackend\Process\Repository\Process::QUERY_REASSIGN_PROCESS_REQUESTS, [
255            'newProcessId' => $newProcessId,
256            'processId' => $processId
257        ]);
258    }
264    protected function writeNewProcess(\BO\Zmsentities\Process $process, \DateTimeInterface $dateTime, int $parentProcess = 0, int $childProcessCount = 0, bool $retry = true, \BO\Zmsentities\Useraccount|null $userAccount = null)
265    {
266        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::INSERT);
267        $process->id = $this->readNewProcessId();
268        $process->setRandomAuthKey();
269        $process->createTimestamp = $dateTime->getTimestamp();
270        if ($parentProcess !== 0) {
271            $process->displayNumber = '';
272        }
273
274        $query->addValuesNewProcess($process, $parentProcess, $childProcessCount);
275
276        if ($parentProcess === 0 && !empty($process->getDisplayNumber())) {
277            $query->addValueDisplayNumber($process);
278        }
279
280        $query->addValuesScopeData($process);
281        $query->addValuesAppointmentData($process);
282        $query->addValuesUpdateProcess($process, $dateTime, $parentProcess);
283        try {
284            $this->writeItem($query);
285        } catch (\BO\Zmsbackend\Exception\Pdo\PDOFailed $exception) {
286            if ($retry) {
287        // First try might fail if two processes are created with the same number at the same time
288                sleep(1);
289        // Let the other process complete his transaction
290                return $this->writeNewProcess($process, $dateTime, $parentProcess, $childProcessCount, false, $userAccount);
291            }
292            throw new \BO\Zmsbackend\Process\Exception\ProcessCreateFailed($exception->getMessage());
293        }
294        (new \BO\Zmsbackend\Slot\Service\Slot())->writeSlotProcessMappingFor($process->id);
295        $checksum = ($userAccount) ? sha1($process->id . '-' . $userAccount->getId()) : '';
296
297        \BO\Zmsbackend\Log\Service\Log::writeProcessLog("CREATE (\BO\Zmsbackend\Process\Service\Process::writeNewProcess) $process $checksum ", \BO\Zmsbackend\Log\Service\Log::ACTION_NEW, $process, $userAccount);
298        return $process;
299    }
305    protected function readNewProcessId()
306    {
307        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
308        $newProcessId = $this->fetchValue($query->getQueryNewProcessId());
309        return $newProcessId;
310    }
317    public function readAuthKeyByProcessId($processId)
318    {
319        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
320        $query
321            ->addEntityMapping()
322            ->addResolvedReferences(0)
323            ->addConditionProcessId($processId);
324        $process = $this->fetchOne($query, new Entity());
325        return ($process->hasId()) ? array(
326            'authKey' => $process->authKey
327        ) : null;
328    }
330    protected function readList($statement, int $resolveReferences): Collection
331    {
332        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
333        $processList = new Collection();
334        while ($processData = $statement->fetch(\PDO::FETCH_ASSOC)) {
335            $entity = new Entity($query->postProcessJoins($processData));
336            $entity = $this->readResolvedReferences($entity, $resolveReferences);
337            $processList->addEntity($entity);
338        }
339        return $processList;
340    }
342    protected function readProcessesWithoutRequests($statement): Collection
343    {
344        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
345        $processList = new Collection();
346        while ($processData = $statement->fetch(\PDO::FETCH_ASSOC)) {
347            $processList->addEntity(new Entity($query->postProcessJoins($processData)));
348        }
349        return $processList;
350    }
352    protected function attachAllRequestsInOneQuery(Collection $processList, int $resolveReferences): Collection
353    {
354        if ($processList->count() === 0 || $resolveReferences < 0) {
355            return $processList;
356        }
357
358        $processIds = [];
359        foreach ($processList as $process) {
360            if ($process->hasId()) {
361                $processIds[] = $process->getId();
362            }
363        }
364
365        $requestsByProcessId = (new \BO\Zmsbackend\Request\Service\Request())
366            ->readAllRequestsForProcessIds($processIds, $resolveReferences - 1);
367
368        foreach ($processList as $process) {
369            if (!$process->hasId()) {
370                continue;
371            }
372            $process->requests = $requestsByProcessId[(int) $process->getId()]
373                ?? new \BO\Zmsentities\Collection\RequestList();
374        }
375
376        return $processList;
377    }
382    public function readEntityList($processId, int $resolveReferences = 0)
383    {
384        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
385        $query
386            ->addResolvedReferences($resolveReferences)
387            ->addEntityMapping()
388            ->addConditionProcessIdFollow($processId);
389        $statement = $this->fetchStatement($query);
390        return $this->readList($statement, $resolveReferences)->sortByAppointmentDate();
391    }
393    public function readByWorkstation(\BO\Zmsentities\Workstation $workstation, int $resolveReferences = 0)
394    {
395        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
396        $query
397            ->addResolvedReferences($resolveReferences)
398            ->addEntityMapping()
399            ->addConditionWorkstationId($workstation->id);
400        $process = $this->fetchOne($query, new Entity());
401        return ($process->hasId()) ? $this->readResolvedReferences($process, $resolveReferences) : null;
402    }
413    public function readProcessListByScopesAndTime(
414        $scopeIds,
415        \DateTimeInterface $dateTime,
416        int $resolveReferences = 0,
417        $withEntities = []
418    ) {
419        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT, '', false, null, $withEntities);
420        $query
421            ->addResolvedReferences($resolveReferences)
422            ->addEntityMapping()
423            ->addConditionScopeIds($scopeIds)
424            ->addConditionAssigned()
425            ->addConditionIgnoreSlots()
426            ->addConditionTime($dateTime)
427            ->removeDuplicates();
428        $statement = $this->fetchStatement($query);
429        return $this->readList($statement, $resolveReferences);
430    }
443    public function readConflictListByScopeAndTime(\BO\Zmsentities\Scope $scope, ?\DateTimeInterface $startDate = null, ?\DateTimeInterface $endDate = null, ?\DateTimeInterface $now = null, int $resolveReferences = 1)
444    {
445        $availabilityList = (new \BO\Zmsbackend\Availability\Service\Availability())
446            ->readAvailabilityListByScope($scope, 0, $startDate, $endDate)
447            ->withScope($scope);
448        if (! $endDate) {
449            $availabilityList = $availabilityList->withDateTime($startDate);
450            $endDate = $startDate;
451        }
452        $currentDate = ($startDate) ? $startDate : $now;
453        $conflictList = $availabilityList->getConflicts($startDate, $endDate);
454        while ($currentDate <= $endDate) {
455            $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
456            $query
457                ->addResolvedReferences($resolveReferences)
458                ->addEntityMapping()
459                ->addConditionScopeId($scope->getId())
460                ->addConditionAssigned()
461                ->addConditionIgnoreSlots();
462            $query->addConditionTime($currentDate);
463            $statement = $this->fetchStatement($query);
464            $processList = $this->readList($statement, $resolveReferences);
465            $processList = $processList->toQueueList($currentDate)->withoutStatus(['queued'])->toProcessList();
466            $conflictList->addList($processList->withOutAvailability($availabilityList));
467            $currentDate = $currentDate->modify('+1 day');
468        }
469        $conflictList = $conflictList->withoutExpiredAppointmentDate($now);
470        return $conflictList;
471    }
480    public function readProcessListByScopeAndStatus(int $scopeId, string $status, int $resolveReferences = 0, int $limit = 1000, ?int $offset = null)
481    {
482        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
483        $query
484            ->addResolvedReferences($resolveReferences)
485            ->addEntityMapping()
486            ->addConditionScopeId($scopeId) //removed because of dismatching scope and pickup scope
487            ->addConditionStatus($status)
488            ->addLimit($limit, $offset);
489        $statement = $this->fetchStatement($query);
490        return $this->readList($statement, $resolveReferences);
491    }
501    public function readProcessListByClusterAndTime($clusterId, \DateTimeInterface $dateTime)
502    {
503        $cluster = (new \BO\Zmsbackend\Cluster\Service\Cluster())->readEntity($clusterId, 1);
504        $scopeIds = [];
505        if ($cluster->scopes->count()) {
506            foreach ($cluster->scopes as $scope) {
507                $scopeIds[] = $scope->id;
508            }
509        }
510
511        return $this->readProcessListByScopesAndTime($scopeIds, $dateTime);
512    }
521    public function readProcessListCountByScope($scopeId)
522    {
523        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
524        $query
525            ->addCountValue()
526            ->addConditionAssigned()
527            ->addConditionIgnoreSlots()
528            ->addConditionScopeId($scopeId);
529        $statement = $this->fetchStatement($query);
530        $result = $statement->fetch(\PDO::FETCH_ASSOC);
531        return $result['processCount'];
532    }
539    public function readProcessListByMailAddress(string $mailAddress, ?int $scopeId = null, int $resolveReferences = 0, int $limit = 2000): Collection
540    {
541        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
542        $query
543            ->addResolvedReferences($resolveReferences)
544            ->addEntityMapping()
545            ->addConditionMail($mailAddress, true)
546            ->addConditionIgnoreSlots()
547            ->addLimit($limit);
548        if ($scopeId) {
549            $query->addConditionScopeId($scopeId);
550        }
551
552        $statement = $this->fetchStatement($query);
553        return $this->readList($statement, $resolveReferences);
554    }
562    public function readListByMailAndStatusList(string $mailAddress, array $statusList, int $resolveReferences = 1, int $limit = 300): Collection
563    {
564        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
565        $query
566            ->addResolvedReferences($resolveReferences)
567            ->addEntityMapping()
568            ->addConditionMail($mailAddress, true)
569            ->addConditionIgnoreSlots()
570            ->addLimit($limit);
571        $statement = $this->fetchStatement($query);
572        $collection = $this->readList($statement, $resolveReferences);
573        return $collection->toProcessListByStatusList($statusList);
574    }
585    public function updateProcessStatus(Entity $process, string $status, \DateTimeInterface $dateTime, int $resolveReferences = 0, $userAccount = null)
586    {
587        $process = (new ProcessStatus())
588            ->writeUpdatedStatus($process, $status, $dateTime, $resolveReferences, $userAccount);
589
590        /** @var Entity $process */
591        if ($this->shouldUpdateDisplayNumber($process, $status)) {
592            $this->updateEntityDisplayNumber($process);
593        }
594
595        return $process;
596    }
598    public function shouldUpdateDisplayNumber(Entity $process, $status): bool
599    {
600        if ($status !== 'preconfirmed' && $status !== 'confirmed') {
601            return false;
602        }
603        $displayNumberPrefix = $process->scope->getPreference('queue', 'displayNumberPrefix');
604        if (empty($displayNumberPrefix)) {
605            return false;
606        }
607
608        if (str_starts_with($process->getDisplayNumber(), $displayNumberPrefix)) {
609            return false;
610        }
611
612        return true;
613    }
622    public function writeDeletedEntity($processId): bool
623    {
624        $processEntityList = $this->readEntityList($processId);
625        $status = false;
626        if ($processEntityList->count()) {
627            foreach ($processEntityList as $entity) {
628                $entityId = $entity->getId();
629                $query = \BO\Zmsbackend\Process\Repository\Process::QUERY_DELETE;
630                $status = $this->perform($query, array(
631                    $entityId,
632                    $entityId
633                ));
634                if ($status) {
635                            $this->deleteRequestsForProcessId($entityId);
636                            (new \BO\Zmsbackend\Slot\Service\Slot())->deleteSlotProcessMappingFor($entityId);
637                            \BO\Zmsbackend\Log\Service\Log::writeProcessLog("DELETE (\BO\Zmsbackend\Process\Service\Process::writeDeletedEntity) $entityId ", \BO\Zmsbackend\Log\Service\Log::ACTION_DELETED, $entity);
638                }
639            }
640        }
641
642        return $status;
643    }
654    public function deleteEntity($processId, $authKey)
655    {
656        return $this->writeCanceledEntity($processId, $authKey);
657    }
667    public function writeCanceledEntity($processId, $authKey, $now = null, ?\BO\Zmsentities\Useraccount $useraccount = null)
668    {
669        $canceledTimestamp = ($now) ? $now->getTimestamp() : (new \DateTimeImmutable())->getTimestamp();
670        $newAuthKey = bin2hex(random_bytes(32));
671        $query = \BO\Zmsbackend\Process\Repository\Process::QUERY_CANCELED;
672        $this->perform($query, [
673            'processId' => $processId,
674            'authKey' => $authKey,
675            'canceledTimestamp' => $canceledTimestamp,
676            'newAuthKey' => $newAuthKey
677        ]);
678        $process = $this->readEntity($processId, new \BO\Zmsbackend\Helper\NoAuth(), 0);
679        \BO\Zmsbackend\Log\Service\Log::writeProcessLog("DELETE (\BO\Zmsbackend\Process\Service\Process::writeCanceledEntity) $processId ", \BO\Zmsbackend\Log\Service\Log::ACTION_CANCELED, $process, $useraccount);
680        return $process;
681    }
692    public function writeBlockedEntity(\BO\Zmsentities\Process $process, bool $releaseSlots = false, ?\BO\Zmsentities\Useraccount $useraccount = null)
693    {
694        $amendment = $process->toDerefencedAmendment();
695        $customTextfield = $process->toDerefencedcustomTextfield();
696        $customTextfield2 = $process->toDerefencedcustomTextfield2();
697        if (!isset($process->queue['status'])) {
698            $process->queue['status'] = $process->status;
699        }
700        $process->status = 'blocked';
701        $query = \BO\Zmsbackend\Process\Repository\Process::QUERY_DEREFERENCED;
702        $status = $this->perform($query, array(
703            $amendment,
704            $customTextfield,
705            $customTextfield2,
706            $process->id,
707            $process->authKey,
708            $process->id
709        ));
710        if ($status) {
711            $processEntityList = $this->readEntityList($process->id);
712            if ($processEntityList->count()) {
713                foreach ($processEntityList as $entity) {
714                    $entityId = $entity->getId();
715                    if ($releaseSlots) {
716                        (new \BO\Zmsbackend\Slot\Service\Slot())->deleteSlotProcessMappingFor($entityId);
717                    }
718                    \BO\Zmsbackend\Log\Service\Log::writeProcessLog("DELETE (\BO\Zmsbackend\Process\Service\Process::writeBlockedEntity) $entityId ", \BO\Zmsbackend\Log\Service\Log::ACTION_DELETED, $process, $useraccount);
719                }
720            }
721        }
722        return $status;
723    }
725    protected function writeRequestsToDb(\BO\Zmsentities\Process $process): void
726    {
727        // Beware of resolveReferences=0 to not delete the existing requests, except for queued processes
728        $hasRequests = ($process->requests && count($process->requests));
729        if ($hasRequests || 'queued' == $process->status) {
730            $this->deleteRequestsForProcessId($process->id);
731        }
732        if ($hasRequests) {
733            $query = new \BO\Zmsbackend\Request\Repository\XRequest(\BO\Zmsbackend\Query\Base::INSERT);
734            foreach ($process->requests as $request) {
735                if ($request->id >= 0) {
736        // allow deleting requests with a -1 request
737                    $query->addValues([
738                            'AnliegenID' => $request['id'],
739                            'source' => $request['source'],
740                            'BuergerID' => $process->id
741                        ]);
742                    $this->writeItem($query);
743                }
744            }
745        }
746    }
748    protected function deleteRequestsForProcessId($processId): bool
749    {
750        $status = false;
751        if (0 < $processId) {
752            $query = new \BO\Zmsbackend\Request\Repository\XRequest(\BO\Zmsbackend\Query\Base::DELETE);
753            $query->addConditionProcessId($processId);
754            $status = $this->deleteItem($query);
755        }
756        return $status;
757    }
759    public function readExpiredProcessList(\DateTimeInterface $expirationDate, int $limit = 500, int $resolveReferences = 0, ?int $offset = null)
760    {
761        $selectQuery = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
762        $selectQuery
763            ->addEntityMapping()
764            ->addResolvedReferences($resolveReferences)
765            ->addConditionProcessDeleteInterval($expirationDate)
766            ->addConditionIgnoreSlots()
767            ->addLimit($limit, $offset);
768        $statement = $this->fetchStatement($selectQuery);
769        return $this->readList($statement, $resolveReferences);
770    }
771    public function readUnconfirmedProcessList(\DateTimeInterface $expirationDate, $scopeId = 0, int $limit = 500, ?int $offset = null, int $resolveReferences = 0)
772    {
773
774        $selectQuery = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
775        $selectQuery
776            ->addEntityMapping()
777            ->addResolvedReferences($resolveReferences)
778            ->addConditionScopeId($scopeId)
779            ->addConditionProcessExpiredIPTimeStamp($expirationDate)
780            ->addConditionStatus('preconfirmed')
781            ->addConditionIgnoreSlots()
782            ->addLimit($limit, $offset);
783        $statement = $this->fetchStatement($selectQuery);
784        return $this->readList($statement, $resolveReferences);
785    }
787    public function readExpiredProcessListByStatus(\DateTimeInterface $expirationDate, $status, int $limit = 500, ?int $offset = null, int $resolveReferences = 0)
788    {
789        $selectQuery = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
790        $selectQuery
791            ->addEntityMapping()
792            ->addResolvedReferences($resolveReferences)
793            ->addConditionProcessDeleteBeforeDate($expirationDate)
794            ->addConditionStatus($status)
795            ->addConditionIgnoreSlots()
796            ->addLimit($limit, $offset);
797        $statement = $this->fetchStatement($selectQuery);
798        return $this->readList($statement, $resolveReferences);
799    }
801    public function readExpiredReservationsList(\DateTimeInterface $expirationDate, $scopeId, int $limit = 500, ?int $offset = null, int $resolveReferences = 0)
802    {
803        $selectQuery = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
804        $selectQuery
805            ->addEntityMapping()
806            ->addResolvedReferences($resolveReferences)
807            ->addConditionScopeId($scopeId)
808            ->addConditionIsReserved()
809            ->addConditionProcessExpiredIPTimeStamp($expirationDate)
810            ->addConditionIgnoreSlots()
811            ->addLimit($limit, $offset);
812        $statement = $this->fetchStatement($selectQuery);
813        return $this->readList($statement, $resolveReferences);
814    }
816    public function readEmailReminderProcessListByInterval(\DateTimeInterface $now, \DateTimeInterface $lastRun, int $defaultReminderInMinutes, int $limit = 500, ?int $offset = null, int $resolveReferences = 0)
817    {
818        $selectQuery = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
819        $selectQuery
820            ->addEntityMapping()
821            ->addResolvedReferences($resolveReferences)
822            ->addConditionProcessMailReminder($now, $lastRun, $defaultReminderInMinutes)
823            ->addConditionAssigned()
824            ->addConditionIgnoreSlots()
825            ->addConditionStatus('confirmed')
826            ->addLimit($limit, $offset);
827        $statement = $this->fetchStatement($selectQuery);
828        return $this->readList($statement, $resolveReferences)->withDepartmentHasMailFrom();
829    }
831    public function readDeallocateProcessList(\DateTimeInterface $now, int $limit = 500, ?int $offset = null, int $resolveReferences = 0)
832    {
833        $selectQuery = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
834        $selectQuery
835            ->addEntityMapping()
836            ->addResolvedReferences($resolveReferences)
837            ->addConditionDeallocate($now)
838            ->addConditionIgnoreSlots()
839            ->addLimit($limit, $offset);
840        $statement = $this->fetchStatement($selectQuery);
841        return $this->readList($statement, $resolveReferences);
842    }
844    public function isAppointmentAllowedWithSameMail(Entity $entity): bool
845    {
846        if (empty($entity->getClients()) || empty($entity->getClients()->getFirst())) {
847            return true;
848        }
849
850        $maxAppointmentsPerMail = $entity->scope->getAppointmentsPerMail();
851        $emailToCheck = $entity->getClients()->getFirst()->email;
852        if ($maxAppointmentsPerMail < 1 || empty($emailToCheck)) {
853            return true;
854        }
855
856        if ($this->isMailWhitelisted($emailToCheck, $entity->scope)) {
857            return true;
858        }
859
860        $processes = $this->readProcessListByMailAddress($entity->getClients()->getFirst()->email, $entity->scope->id);
861        $activeAppointments = 0;
862        foreach ($processes as $process) {
863            // Skip the process under change so list order cannot short-circuit the limit
864            // check (a reserved sibling with a lower id used to make preconfirm succeed).
865            if ($entity->id == $process->id) {
866                continue;
867            }
868
869            if (in_array($process->getStatus(), ['preconfirmed', 'confirmed'])) {
870                $activeAppointments++;
871                if ($activeAppointments >= $maxAppointmentsPerMail) {
872                    return false;
873                }
874            }
875        }
876
877        return true;
878    }
880    protected function isMailWhitelisted(string $email, ScopeEntity $scope): bool
881    {
882        $emailsWithNoLimit = explode(',', $scope->getWhitelistedMails());
883        if (empty($emailsWithNoLimit)) {
884            return false;
885        }
886
887        foreach ($emailsWithNoLimit as $whitelistedMail) {
888            $whitelistedMail = trim($whitelistedMail);
889            if ($email === $whitelistedMail) {
890                return true;
891            }
892
893            if (str_starts_with($whitelistedMail, '@') && str_contains($email, $whitelistedMail)) {
894                return true;
895            }
896        }
897
898        return false;
899    }
901    public function readProcessWithSameDayAndDisplayNumber($scopeId, $displayNumber, $date)
902    {
903        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
904        $query->addEntityMapping()
905            ->addResolvedReferences(1)
906            ->addConditionScopeId($scopeId)
907            ->addConditionDate($date)
908            ->addConditionDisplayNumber($displayNumber)
909            ->addLimit(1);
910
911        $process = $this->fetchOne($query, new Entity());
912
913        return $this->readResolvedReferences($process, 1);
914    }
916    public function readProcessListByExternalUserId(string $externalUserId, ?int $filterId = null, ?string $status = null, int $resolveReferences = 0, int $limit = 1000): Collection
917    {
918        $query = new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT);
919        $query
920            ->addResolvedReferences($resolveReferences)
921            ->addEntityMapping()
922            ->addConditionExternalUserId($externalUserId);
923        if (!is_null($filterId)) {
924            $query->addConditionProcessId($filterId);
925        }
926        if (!is_null($status)) {
927            $query->addConditionStatus($status);
928        }
929        $query
930            ->addLimit($limit);
931
932        $statement = $this->fetchStatement($query);
933        $processList = $this->readProcessesWithoutRequests($statement);
934        return $this->attachAllRequestsInOneQuery($processList, (int) $resolveReferences);
935    }
937    public function readAssignedWorkstationIdForUpdate(int $processId): ?int
938    {
939        $query = (new \BO\Zmsbackend\Process\Repository\Process(\BO\Zmsbackend\Query\Base::SELECT))->getLockAssignedWorkstationId();
940        $value = $this->fetchValue($query, ['processId' => $processId]);
941
942        if ($value === false || $value === null) {
943            return null;
944        }
945
946        return (int) $value;
947    }

Inherited from BO\Zmsbackend\Base

38    public function __construct(?Connection\Pdo $writeConnection = null, ?Connection\Pdo $readConnection = null)
39    {
40        $this->writeDb = $writeConnection;
41        $this->readDb = $readConnection;
42    }
44    public static function init(?Connection\Pdo $writeConnection = null, ?Connection\Pdo $readConnection = null): static
45    {
46        $instance = new static($writeConnection, $readConnection);
47        return $instance;
48    }
50    public function getWriter(): Connection\Pdo
51    {
52        if (null === $this->writeDb) {
53            $this->writeDb = Connection\Select::getWriteConnection();
54            $this->readDb = $this->writeDb;
55        }
56        return $this->writeDb;
57    }
59    public function getReader(): Connection\Pdo
60    {
61        if (null !== $this->writeDb) {
62            // if readDB gets a reset, still use writeDB
63            return $this->writeDb;
64        }
65        if (null === $this->readDb) {
66            $this->readDb = Connection\Select::getReadConnection();
67        }
68        return $this->readDb;
69    }
71    public function fetchPreparedStatement(Query\Base|string $query)
72    {
73        $sql = "$query";
74        $reader = $this->getReader();
75        if (spl_object_hash($reader) != static::$preparedConnectionId) {
76            // do not use prepared statements on a different connection
77            static::$preparedCache = [];
78            static::$preparedConnectionId = spl_object_hash($reader);
79        }
80        if (!isset(static::$preparedCache[$sql])) {
81            $prepared = $this->getReader()->prepare($sql);
82            static::$preparedCache[$sql] = $prepared;
83        }
84        return static::$preparedCache[$sql];
85    }
87    public function startExecute($statement, array $parameters)
88    {
89        $statement = static::pdoExceptionHandler(function () use ($statement, $parameters) {
90            $statement->execute($parameters);
91            return $statement;
92        });
93        return $statement;
94    }
96    protected static function pdoExceptionHandler(\Closure $pdoFunction, $parameters = [])
97    {
98        try {
99            $statement = $pdoFunction($parameters);
100        } catch (\PDOException $pdoException) {
101            if (stripos($pdoException->getMessage(), 'Lock wait timeout') !== false) {
102                throw new \BO\Zmsbackend\Exception\Pdo\LockTimeout();
103            }
104            //@codeCoverageIgnoreStart
105            if (stripos($pdoException->getMessage(), 'Deadlock found') !== false) {
106                throw new \BO\Zmsbackend\Exception\Pdo\DeadLockFound();
107            }
108            //@codeCoverageIgnoreEnd
109            $message = "SQL: "
110                . " Err: "
111                . $pdoException->getMessage()
112                //. " || Statement: "
113                //.$statement->queryString
114                //." || Parameters=". var_export($parameters, true)
115                ;
116            throw new \BO\Zmsbackend\Exception\Pdo\PDOFailed($message, 0, $pdoException);
117        }
118        return $statement;
119    }
121    public function fetchStatement(Query\Base $query)
122    {
123        $parameters = $query->getParameters();
124        $statement = $this->startExecute($this->fetchPreparedStatement($query), $parameters);
125        return $statement;
126    }
133    public function fetchOne(Query\Base $query, \BO\Zmsentities\Schema\Entity $entity)
134    {
135        $statement = $this->fetchStatement($query);
136        $data = $statement->fetch(\PDO::FETCH_ASSOC);
137        if ($data) {
138            $entity->exchangeArray($query->postProcessJoins($data));
139            $entity->setResolveLevel($query->getResolveLevel());
140        }
141        $statement->closeCursor();
142        return $entity;
143    }
145    public function fetchRow(Query\Base|string $query, array|null $parameters = null)
146    {
147        return static::pdoExceptionHandler(function () use ($query, $parameters) {
148            $prepared = $this->fetchPreparedStatement($query);
149            $prepared->execute($parameters);
150            $row = $prepared->fetch(\PDO::FETCH_ASSOC);
151            $prepared->closeCursor();
152            return $row;
153        });
154    }
156    public function fetchValue(Query\Base|string $query, array|null $parameters = null)
157    {
158        return static::pdoExceptionHandler(function () use ($query, $parameters) {
159            $prepared = $this->fetchPreparedStatement($query);
160            $prepared->execute($parameters);
161            $value = $prepared->fetchColumn();
162            $prepared->closeCursor();
163            return $value;
164        });
165    }
167    public function fetchAll(Query\Base|string $query, array|null $parameters = null)
168    {
169        return static::pdoExceptionHandler(function () use ($query, $parameters) {
170            $prepared = $this->fetchPreparedStatement($query);
171            $prepared->execute($parameters);
172            $list = $prepared->fetchAll(\PDO::FETCH_ASSOC);
173            $prepared->closeCursor();
174            return $list;
175        });
176    }
178    public function fetchHandle(Query\Base|string $query, array|null $parameters = null)
179    {
180        return static::pdoExceptionHandler(function () use ($query, $parameters) {
181            $prepared = $this->fetchPreparedStatement($query);
182            $prepared->execute($parameters);
183            return $prepared;
184        });
185    }
187    public function fetchList(Query\Base $query, \BO\Zmsentities\Schema\Entity $entity, array|\ArrayAccess $resultList = [])
188    {
189        $statement = $this->fetchStatement($query);
190        while ($data = $statement->fetch(\PDO::FETCH_ASSOC)) {
191            $dataEntity = clone $entity;
192            $dataEntity->exchangeArray($query->postProcessJoins($data));
193            $dataEntity->setResolveLevel($query->getResolveLevel());
194            $resultList[] = $dataEntity;
195        }
196        $statement->closeCursor();
197        return $resultList;
198    }
206    public function writeItem(Query\Base $query)
207    {
208        return static::pdoExceptionHandler(function () use ($query) {
209            $this->getWriter(); //Switch to writer for write/delete
210            $statement = $this->fetchPreparedStatement($query);
211            $status = $statement->execute($query->getParameters());
212            $statement->closeCursor();
213            return $status;
214        });
215    }
221    public function deleteItem(Query\Base $query)
222    {
223        return $this->writeItem($query);
224    }
226    public function perform(string $sql, array|null $parameters = null)
227    {
228        return static::pdoExceptionHandler(function () use ($sql, $parameters) {
229            $this->getWriter(); //Switch to writer for perform
230            $prepared = $this->fetchPreparedStatement($sql);
231            $status = $prepared->execute($parameters);
232            $prepared->closeCursor();
233            return $status;
234        });
235    }
237    public function fetchAffected(string $sql, array|null $parameters = null)
238    {
239        return static::pdoExceptionHandler(function () use ($sql, $parameters) {
240            $this->getWriter(); //Switch to writer for perform
241            $prepared = $this->fetchPreparedStatement($sql);
242            $prepared->execute($parameters);
243            $count = $prepared->rowCount();
244            $prepared->closeCursor();
245            return $count;
246        });
247    }
268    public function hashStringValue(string $value, ?callable $callable = null): string
269    {
270        if ($callable === null) {
271            $callable = 'sha1';
272        }
273
274        if (is_callable($callable)) {
275            $hash = $callable($value);
276
277            if (is_string($callable)) {
278                return $callable . ':' . $hash;
279            }
280            if ($callable instanceof \Closure) {
281                return 'closure:' . $hash;
282            }
283            // else
284            return 'custom:' . $hash;
285        }
286
287        throw new \InvalidArgumentException('hashStringValue() should be called with a callable as second parameter.');
288    }