Lines 94.02% 126 / 134
Methods 90.62% 29 / 32
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 getEntityMapping 0.00% 0 / 2 0.00% 0 / 1 2
 reverseEntityMapping 100.00% 13 / 13 100.00% 1 / 1 3
 addConditionSlotId 0.00% 0 / 2 0.00% 0 / 1 2
 [BO\Zmsbackend\Query\Base] __construct 88.23% 30 / 34 0.00% 0 / 1 10.16
 [BO\Zmsbackend\Query\Base] __toString n/a 0 / 0 n/a 0 / 0 4
 [BO\Zmsbackend\Query\Base] getName 100.00% 1 / 1 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] addSelect 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] setDistinctSelect 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] setResolveLevel 100.00% 3 / 3 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] getResolveLevel 100.00% 3 / 3 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] getAlias 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] getTablename 100.00% 3 / 3 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] addTable 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] addTableAlias 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] addRequiredJoins 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] addResolvedReferences 100.00% 11 / 11 100.00% 1 / 1 3
 [BO\Zmsbackend\Query\Base] setWithEntities 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] addJoin 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] leftJoin 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] innerJoin 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] getSql 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] getParameters 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] getReferenceMapping 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] expression 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] addEntityMapping 100.00% 3 / 3 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] getPrefixed 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] getPrefixedList 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] addReferenceMapping 100.00% 3 / 3 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] addLimit 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] addValues 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] postProcess 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Query\Base] postProcessJoins 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] shouldLoadEntity 100.00% 3 / 3 100.00% 1 / 1 2
5class Slot extends \BO\Zmsbackend\Query\Base implements \BO\Zmsbackend\Query\MappingInterface
6{
7    /**
8     *
9     * @var String TABLE mysql table reference
10     */
11    const string TABLE = 'slot';
12
13    const string QUERY_OPTIMIZE_SLOT = 'OPTIMIZE TABLE slot;';
14    const string QUERY_OPTIMIZE_SLOT_HIERA = 'OPTIMIZE TABLE slot_hiera;';
15    const string QUERY_OPTIMIZE_SLOT_PROCESS = 'OPTIMIZE TABLE slot_proces;';
16    const string QUERY_OPTIMIZE_PROCESS = 'OPTIMIZE TABLE buerger;';
17
18    const string QUERY_LAST_CHANGED = 'SELECT MAX(updateTimestamp) AS dateString FROM slot;';
19
20    const string QUERY_LAST_CHANGED_AVAILABILITY = '
21        SELECT MAX(updateTimestamp) AS dateString FROM slot WHERE availabilityID = :availabilityID AND status="free";';
22
23    const string QUERY_LAST_IN_AVAILABILITY = '
24        SELECT CONCAT(year, "-", LPAD(month, 2, "0"), "-", LPAD(day, 2, "0")) AS dateString
25        FROM slot
26        WHERE availabilityID = :availabilityID AND status="free"
27        ORDER BY year DESC, month DESC, day DESC
28        LIMIT 1;';
29
30    const string QUERY_OLDEST_VERSION_IN_AVAILABILITY = '
31        SELECT `version`
32        FROM slot
33        WHERE availabilityID = :availabilityID AND status="free"
34        ORDER BY `version` ASC
35        LIMIT 1;';
36
37    const string QUERY_LAST_CHANGED_SCOPE = '
38        SELECT MAX(updateTimestamp) AS dateString FROM slot WHERE scopeID = :scopeID;';
39
40    const string QUERY_INSERT_SLOT_PROCESS = '
41        INSERT INTO slot_process
42        VALUES(?,?,?) 
43    ';
44
45    const string QUERY_SELECT_BY_SCOPE_AND_DAY = '
46        SELECT
47            s.*
48        FROM slot s
49        WHERE
50            s.scopeID = :scopeID
51            AND s.year = :year
52            AND s.month = :month
53            AND s.day = :day
54    ';
55
56    const string QUERY_SELECT_MISSING_PROCESS = '
57        SELECT 
58          s.slotID,
59          b.BuergerID,
60          NOW() updateTimestamp
61        FROM slot s
62          INNER JOIN buerger b ON
63            s.year = YEAR(b.Datum)
64            AND s.month = MONTH(b.Datum)
65            AND s.day = DAY(b.Datum)
66            AND s.scopeID = b.StandortID
67            AND b.Uhrzeit BETWEEN s.time AND SEC_TO_TIME(TIME_TO_SEC(s.time) + (s.slotTimeInMinutes * 60) - 1)
68            AND s.status = "free"
69          LEFT JOIN slot_process sp ON b.BuergerID = sp.processID
70        WHERE
71          sp.processID IS NULL
72    ';
73    const string QUERY_SELECT_MISSING_PROCESS_BY_SCOPE = '
74          AND s.scopeID = :scopeID
75    ';
76
77
78    const string QUERY_INSERT_SLOT_PROCESS_ID = '
79        REPLACE INTO slot_process
80        SELECT 
81          s.slotID,
82          b.BuergerID,
83          NOW()
84        FROM slot s
85          INNER JOIN buerger b ON
86            s.year = YEAR(b.Datum)
87            AND s.month = MONTH(b.Datum)
88            AND s.day = DAY(b.Datum)
89            AND s.scopeID = b.StandortID
90            AND s.status = "free"
91            AND b.Uhrzeit BETWEEN s.time AND SEC_TO_TIME(TIME_TO_SEC(s.time) + (s.slotTimeInMinutes * 60) - 1)
92        WHERE
93          b.BuergerID = :processId
94    ';
95    const string QUERY_DELETE_SLOT_PROCESS_CANCELLED = '
96        DELETE sp 
97            FROM slot_process sp LEFT JOIN slot s USING (slotID)
98            WHERE (s.status = "cancelled" OR s.status IS NULL)
99    ';
100    const string QUERY_DELETE_SLOT_PROCESS_CANCELLED_BY_SCOPE = '
101                AND s.scopeID = :scopeID
102    ';
103
104
105    const string QUERY_UPDATE_SLOT_MISSING_AVAILABILITY_BY_SCOPE = '
106    UPDATE
107         slot s
108           LEFT JOIN oeffnungszeit a ON s.availabilityID = a.OeffnungszeitID
109           SET s.status = "cancelled"
110           WHERE
111             (
112               a.OeffnungszeitID IS NULL
113               OR a.end_date < :dateString
114             )
115             AND s.scopeID = :scopeID
116    ';
117
118    const string QUERY_UPDATE_SLOT_MISSING_AVAILABILITY = '
119    UPDATE
120         slot s
121           LEFT JOIN oeffnungszeit a ON s.availabilityID = a.OeffnungszeitID
122           SET s.status = "cancelled"
123           WHERE
124             a.OeffnungszeitID IS NULL
125               OR a.end_date < :dateString
126    ';
127
128    const string QUERY_SELECT_DELETABLE_SLOT_PROCESS = '
129        SELECT sp.processID AS processId
130            FROM slot_process sp
131              LEFT JOIN buerger b ON sp.processID = b.BuergerID
132              LEFT JOIN slot s ON sp.slotID = s.slotID
133            WHERE (
134                b.BuergerID IS NULL
135                OR (
136                  b.updateTimestamp > sp.updateTimestamp
137                  AND (
138                    b.Uhrzeit NOT BETWEEN s.time AND SEC_TO_TIME(TIME_TO_SEC(s.time) + (s.slotTimeInMinutes * 60) - 1)
139                    OR s.month != MONTH(b.Datum)
140                    OR s.day != DAY(b.Datum)
141                    OR s.scopeID != b.StandortID
142                  )
143                )
144              ) 
145    ';
146    const string QUERY_SELECT_DELETABLE_SLOT_PROCESS_BY_SCOPE = '
147              AND b.StandortID = :scopeID
148    ';
149
150    const string QUERY_DELETE_SLOT_PROCESS_ID = '
151        DELETE sp 
152            FROM slot_process sp 
153            WHERE sp.processID = :processId
154    ';
155
156    const string QUERY_UPDATE_SLOT_STATUS = "
157        UPDATE slot
158          LEFT JOIN (
159          SELECT s.slotID,
160          IF(s.status IN ('free', 'full'), IF(IFNULL(COUNT(p.slotID), 0) < intern, 'free', 'full'), s.status) newstatus
161          FROM slot s
162            LEFT JOIN slot_process p ON s.slotID = p.slotID
163          GROUP BY s.slotID
164          ) calc ON slot.slotID = calc.slotID
165        SET
166          slot.status = calc.newstatus
167        WHERE slot.status != calc.newstatus
168";
169
170    const string QUERY_SELECT_SLOT = '
171    SELECT slotID FROM slot WHERE
172      scopeID = :scopeID
173      AND year = :year
174      AND month = :month
175      AND day = :day
176      AND time = :time
177      AND availabilityID = :availabilityID
178    LIMIT 1
179';
180
181    /**
182     * Lock multiple appointment slots in one statement.
183     * Placeholders for time IN (...) are injected by Slot::lockSlotsForAppointment.
184     * ORDER BY slotID prefers a deterministic primary-key lock order.
185     */
186    public const string QUERY_SELECT_SLOTS_FOR_UPDATE = '
187    SELECT slotID FROM slot WHERE
188      scopeID = ?
189      AND availabilityID = ?
190      AND year = ?
191      AND month = ?
192      AND day = ?
193      AND time IN (%s)
194    ORDER BY slotID ASC
195    FOR UPDATE
196';
197
198    const string QUERY_INSERT_ANCESTOR = '
199    INSERT INTO slot_hiera SET slotID = :slotID, ancestorID = :ancestorID, ancestorLevel = :ancestorLevel
200';
201
202    const string QUERY_DELETE_ANCESTOR = '
203    DELETE FROM slot_hiera WHERE slotID = :slotID
204';
205
206    const string QUERY_CANCEL_AVAILABILITY = '
207        UPDATE slot SET status = "cancelled" WHERE availabilityID = :availabilityID
208';
209
210    const string QUERY_CANCEL_AVAILABILITY_BEFORE_BOOKABLE = '
211            UPDATE slot SET status = "cancelled" WHERE availabilityID = :availabilityID 
212            AND CONCAT(year, "-", LPAD(month, 2, "0"), "-", LPAD(day, 2, "0")) < :providedDate
213';
214
215    const string QUERY_CANCEL_AVAILABILITY_AFTER_BOOKABLE = '
216            UPDATE slot SET status = "cancelled" WHERE availabilityID = :availabilityID 
217            AND CONCAT(year, "-", LPAD(month, 2, "0"), "-", LPAD(day, 2, "0")) > :providedDate
218    ';
219
220    const string QUERY_CANCEL_SLOT_OLD_BY_SCOPE = '
221    UPDATE slot SET status =  "cancelled" 
222        WHERE scopeID = :scopeID AND (
223            (year < :year)
224            OR (year = :year AND  month < :month) 
225            OR (year = :year AND  month = :month AND  day <= :day AND time < :time)
226        )
227';
228
229    const string QUERY_CANCEL_SLOT_OLD = '
230    UPDATE slot SET status =  "cancelled" 
231        WHERE (year < :year)
232            OR (year = :year AND  month < :month) 
233            OR (year = :year AND  month = :month AND  day <= :day AND time < :time)
234';
235
236    const string QUERY_DELETE_SLOT_OLD = '
237    DELETE FROM slot 
238        WHERE (year < :year) 
239            OR (year = :year AND  month < :month) 
240            OR (year = :year AND  month = :month AND  day < :day)
241';
242
243    const string QUERY_DELETE_SLOT_HIERA = '
244        DELETE sh 
245            FROM slot_hiera sh LEFT JOIN slot s USING(slotID)
246            WHERE s.slotID IS NULL
247    ';
248
249
250    /**
251     * @return array
252     *
253     */
254    #[\Override]
255    public function getEntityMapping()
256    {
257        return [
258        ];
259    }
260
261    /**
262     * @return (mixed|string)[]
263     *
264     */
265    public function reverseEntityMapping(
266        \BO\Zmsentities\Slot $slot,
267        \BO\Zmsentities\Availability $availability,
268        \DateTimeInterface $date
269    ): array {
270        $data = array();
271        $data['scopeID'] = $availability->scope->id;
272        $data['availabilityID'] = $availability->id;
273        $data['version'] = $availability->version;
274        $data['year'] = $date->format('Y');
275        $data['month'] = $date->format('m');
276        $data['day'] = $date->format('d');
277        $data['time'] = $slot->getTimeString();
278        $data['public'] = isset($slot['public']) ? $slot['public'] : $availability->workstationCount['public'];
279        $data['intern'] = isset($slot['intern']) ? $slot['intern'] : $availability->workstationCount['intern'];
280        $data['status'] = $slot->status;
281        $data['slotTimeInMinutes'] = $availability->slotTimeInMinutes;
282        return $data;
283    }
284
285    public function addConditionSlotId($slotID): static
286    {
287        $this->query->where('slot.slotID', '=', $slotID);
288        return $this;
289    }
290}

Inherited from BO\Zmsbackend\Query\Base

96    public function __construct(
97        $queryType,
98        $prefix = '',
99        string|false $name = false,
100        mixed $resolveLevel = null,
101        array $withEntities = []
102    ) {
103        $this->prefix = $prefix;
104        $this->name = $name;
105        $this->withEntities = $withEntities;
106        $this->setResolveLevel($resolveLevel);
107        $dialect = new MySQL();
108        if (self::SELECT === $queryType) {
109            $this->query = new Select($dialect);
110            $this->addSelect();
111        } elseif (self::INSERT === $queryType) {
112            $this->query = new Insert($dialect);
113            $this->addTable();
114        } elseif (self::UPDATE === $queryType) {
115            $this->query = new Update($dialect);
116            $this->addTableAlias();
117        } elseif (self::REPLACE === $queryType) {
118            $this->query = new INSERT($dialect);
119            $this->query->queryBaseStatement('REPLACE INTO');
120            $this->addTable();
121        } elseif (self::DELETE === $queryType) {
122            $this->query = new Delete($dialect);
123            $this->query->queryBaseStatement('DELETE ' . $this::getAlias() . ' FROM');
124            $this->addTableAlias();
125        } elseif ($queryType instanceof self) {
126            $this->query = $queryType->query;
127            // Share join-alias state with the parent query (intentional by-ref).
128            /** @psalm-suppress UnsupportedPropertyReferenceUsage */
129            $this->joinedAliasList =& $queryType->joinedAliasList;
130            $this->resolveLevel = $queryType->resolveLevel - 1;
131        } elseif ($queryType instanceof QueryBuilder) {
132            $this->query = $queryType;
133        } else {
134            throw new \InvalidArgumentException(
135                'Unsupported query type for ' . static::class . ': '
136                . (is_object($queryType) ? $queryType::class : gettype($queryType))
137            );
138        }
139        if ($this->query instanceof Select) {
140            $this->addRequiredJoins();
141        }
142    }
147    public function __toString()
148    {
149        if ($this->name) {
150            $name = $this->name . '_' . $this->prefix . (string) $this->resolveLevel;
151            if (!isset(static::$sqlCache[$name])) {
152                static::$sqlCache[$name] = $this->getSql();
153            }
154            return static::$sqlCache[$name];
155        }
156        if ($this->currentSqlString) {
157            $sql = $this->currentSqlString;
158        } else {
159            $sql = $this->getSql();
160        }
161        return $sql;
162    }
164    public function getName()
165    {
166        return $this->name ? $this->name : get_class($this);
167    }
174    protected function addSelect(): static
175    {
176        $table = $this::getTablename();
177        $alias = $this::getAlias();
178        $this->query->from($table, $alias);
179        return $this;
180    }
182    public function setDistinctSelect(): void
183    {
184        $this->query->queryBaseStatement('SELECT DISTINCT');
185    }
187    public function setResolveLevel(int|null $resolveLevel): static
188    {
189        if ($resolveLevel !== null) {
190            $this->resolveLevel = $resolveLevel;
191        }
192        return $this;
193    }
195    public function getResolveLevel()
196    {
197        if (null === $this->resolveLevel) {
198            throw new \Exception("Required setting for resolveReferenceLevel missing in " . get_class($this));
199        }
200        return $this->resolveLevel;
201    }
210    public static function getAlias()
211    {
212        $class = get_called_class();
213        $alias = constant($class . '::ALIAS');
214        if (null === $alias) {
215            $alias = lcfirst(preg_replace('#^.*\\\#', '', $class));
216        }
217        return $alias;
218    }
225    public static function getTablename()
226    {
227        $class = get_called_class();
228        $table = constant($class . '::TABLE');
229        return $table;
230    }
237    protected function addTable(): static
238    {
239        $table = $this::getTablename();
240        $alias = $this::getAlias();
241        $this->query->table($table, $alias);
242        return $this;
243    }
250    protected function addTableAlias(): static
251    {
252        $table = $this::getTablename();
253        $alias = $this::getAlias();
254        $this->query->table(self::expression($table . ' ' . $alias));
255        return $this;
256    }
264    protected function addRequiredJoins()
265    {
266    }
273    public function addResolvedReferences($depth): static
274    {
275        $this->setResolveLevel($depth);
276        if ($depth > 0) {
277            $queryList = $this->addJoin();
278            foreach ($queryList as $query) {
279                $query->setResolveLevel($depth);
280                $query->setWithEntities($this->withEntities);
281                $query->addResolvedReferences($depth - 1);
282                $query->addEntityMapping();
283            }
284            $this->joinedQueryList = $queryList;
285        } else {
286            $this->addReferenceMapping();
287        }
288        return $this;
289    }
294    public function setWithEntities($withEntities = []): void
295    {
296        $this->withEntities = $withEntities;
297    }
304    protected function addJoin()
305    {
306        return [];
307    }
309    protected function leftJoin(Alias $alias, string|Expression|null $left = null, string|null $operator = null, string|null $right = null): QueryBuilder
310    {
311        $aliasId = $alias->getAliasIdentifier();
312        if (!in_array($aliasId, $this->joinedAliasList)) {
313            $this->joinedAliasList[] = $aliasId;
314            $this->query->leftJoin($alias, $left, $operator, $right);
315        } else {
316            //throw new \Exception("Tried to add Alias ".$aliasId);
317        }
318        return $this->query;
319    }
321    protected function innerJoin(Alias $alias, string|null $left = null, string|null $operator = null, string|null $right = null): QueryBuilder
322    {
323        $aliasId = $alias->getAliasIdentifier();
324        if (!in_array($aliasId, $this->joinedAliasList)) {
325            $this->joinedAliasList[] = $aliasId;
326            $this->query->join($alias, $left, $operator, $right);
327        }
328        return $this->query;
329    }
337    public function getSql()
338    {
339        $this->currentSqlString = (string)$this->query;
340        return $this->currentSqlString;
341    }
348    public function getParameters()
349    {
350        return $this->query->params();
351    }
357    public function getReferenceMapping()
358    {
359        return [
360        ];
361    }
368    protected static function expression(string $string)
369    {
370        return new Expression($string);
371    }
379    public function addEntityMapping(mixed $type = null): static
380    {
381        // Concrete query classes provide getEntityMapping(); not all declare MappingInterface.
382        /** @psalm-suppress UndefinedMethod, TooManyArguments */
383        $entityMapping = $this->getPrefixedList($this->getEntityMapping($type));
384        $this->query->select($entityMapping);
385        return $this;
386    }
388    protected function getPrefixed($prefix): string
389    {
390        return $this->prefix . $prefix;
391    }
393    protected function getPrefixedList(array $unprefixedList): array
394    {
395        $prefixed = [];
396        foreach ($unprefixedList as $key => $value) {
397            $prefixed[$this->getPrefixed($key)] = $value;
398        }
399        return $prefixed;
400    }
405    protected function addReferenceMapping(): static
406    {
407        $referenceMapping = $this->getPrefixedList($this->getReferenceMapping());
408        $this->query->select($referenceMapping);
409        return $this;
410    }
412    public function addLimit(int $count, ?int $offset = null): static
413    {
414        $this->query->limit($count);
415        if ($offset) {
416            $this->query->offset($offset);
417        }
418        return $this;
419    }
424    public function addValues(array $values): static
425    {
426        $this->query->values($values);
427        return $this;
428    }
434    public function postProcess($data)
435    {
436        return $data;
437    }
445    public function postProcessJoins(array $data)
446    {
447        $data = $this->postProcess($data);
448        foreach ($this->joinedQueryList as $query) {
449            $data = $query->postProcess($data);
450        }
451        return $data;
452    }
454    public function shouldLoadEntity(string $name): bool
455    {
456        if (empty($this->withEntities)) {
457            return true;
458        }
459
460        return in_array($name, $this->withEntities);
461    }