Lines 97.86% 183 / 187
Methods 94.59% 35 / 37
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 100.00% 4 / 4 100.00% 1 / 1 2
 getEntityMapping 100.00% 26 / 26 100.00% 1 / 1 1
 addConditionScopeId 100.00% 2 / 2 100.00% 1 / 1 1
 addConditionChangedAtRange 100.00% 3 / 3 100.00% 1 / 1 1
 addConditionAvailabilityId 100.00% 2 / 2 100.00% 1 / 1 1
 addConditionAction 100.00% 2 / 2 100.00% 1 / 1 1
 reverseEntityMapping 100.00% 24 / 24 100.00% 1 / 1 2
 postProcess 100.00% 30 / 30 100.00% 1 / 1 3
 asDateString 66.66% 4 / 6 0.00% 0 / 1 3.33
 asTimeString 66.66% 4 / 6 0.00% 0 / 1 3.33
 [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] postProcessJoins 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsbackend\Query\Base] shouldLoadEntity 100.00% 3 / 3 100.00% 1 / 1 2
9class AvailabilityHistory extends \BO\Zmsbackend\Query\Base implements \BO\Zmsbackend\Query\MappingInterface
10{
11    public const string TABLE = 'availability_history';
12
13    public const string ALIAS = 'availabilityHistory';
14
15    public const string QUERY_DELETE_OLDER_THAN = '
16        DELETE FROM availability_history
17        WHERE changed_at < :cutoff
18    ';
19
20    /** @var list<string> */
21    public const array ALLOWED_ACTIONS = Entity::ACTIONS;
22
23    /** @var int */
24    protected $resolveLevel = 0;
25
26    public function __construct(
27        mixed $queryType,
28        string $prefix = '',
29        string|false $name = false,
30        mixed $resolveLevel = null
31    ) {
32        parent::__construct($queryType, $prefix, $name, $resolveLevel);
33
34        if ($queryType === self::SELECT) {
35            $this->query->orderBy(self::ALIAS . '.changed_at', 'DESC');
36            $this->query->orderBy(self::ALIAS . '.id', 'DESC');
37        }
38    }
39
40    #[\Override]
41    public function getEntityMapping(): array
42    {
43        return [
44            'id' => self::ALIAS . '.id',
45            'scopeId' => self::ALIAS . '.scope_id',
46            'availabilityId' => self::ALIAS . '.availability_id',
47            'action' => self::ALIAS . '.action',
48            'startDate' => self::ALIAS . '.start_date',
49            'endDate' => self::ALIAS . '.end_date',
50            'everyXWeeks' => self::ALIAS . '.every_x_weeks',
51            'everyOtherWeek' => self::ALIAS . '.every_other_week',
52            'weekday' => self::ALIAS . '.weekday',
53            'startTime' => self::ALIAS . '.start_time',
54            'appointmentStartTime' => self::ALIAS . '.appointment_start_time',
55            'endTime' => self::ALIAS . '.end_time',
56            'appointmentEndTime' => self::ALIAS . '.appointment_end_time',
57            'timeSlot' => self::ALIAS . '.time_slot',
58            'workstationCount' => self::ALIAS . '.workstation_count',
59            'appointmentWorkstationCount' => self::ALIAS . '.appointment_workstation_count',
60            'comment' => self::ALIAS . '.comment',
61            'internetReduction' => self::ALIAS . '.internet_reduction',
62            'multipleSlotsAllowed' => self::ALIAS . '.multiple_slots_allowed',
63            'openFromDays' => self::ALIAS . '.open_from_days',
64            'openUntilDays' => self::ALIAS . '.open_until_days',
65            'version' => self::ALIAS . '.version',
66            'changedAt' => self::ALIAS . '.changed_at',
67            'changedBy' => self::ALIAS . '.changed_by',
68        ];
69    }
70
71    public function addConditionScopeId(int $scopeId): self
72    {
73        $this->query->where(self::ALIAS . '.scope_id', '=', $scopeId);
74        return $this;
75    }
76
77    public function addConditionChangedAtRange(\DateTimeInterface $from, \DateTimeInterface $to): self
78    {
79        $this->query->where(self::ALIAS . '.changed_at', '>=', $from->format('Y-m-d H:i:s'));
80        $this->query->where(self::ALIAS . '.changed_at', '<=', $to->format('Y-m-d H:i:s'));
81        return $this;
82    }
83
84    public function addConditionAvailabilityId(int $availabilityId): self
85    {
86        $this->query->where(self::ALIAS . '.availability_id', '=', $availabilityId);
87        return $this;
88    }
89
90    public function addConditionAction(string $action): self
91    {
92        $this->query->where(self::ALIAS . '.action', '=', $action);
93        return $this;
94    }
95
96    public function reverseEntityMapping(array $row): array
97    {
98        return [
99            'scope_id' => (int) $row['scope_id'],
100            'availability_id' => $row['availability_id'] !== null ? (int) $row['availability_id'] : null,
101            'action' => $row['action'],
102            'start_date' => (string) $row['start_date'],
103            'end_date' => (string) $row['end_date'],
104            'every_x_weeks' => (int) ($row['every_x_weeks'] ?? 0),
105            'every_other_week' => (int) ($row['every_other_week'] ?? 0),
106            'weekday' => (int) $row['weekday'],
107            'start_time' => (string) ($row['start_time'] ?? '00:00:00'),
108            'appointment_start_time' => (string) ($row['appointment_start_time'] ?? '00:00:00'),
109            'end_time' => (string) ($row['end_time'] ?? '00:00:00'),
110            'appointment_end_time' => (string) ($row['appointment_end_time'] ?? '00:00:00'),
111            'time_slot' => (string) ($row['time_slot'] ?? '00:00:00'),
112            'workstation_count' => (int) ($row['workstation_count'] ?? 0),
113            'appointment_workstation_count' => (int) ($row['appointment_workstation_count'] ?? 0),
114            'comment' => $row['comment'] ?? null,
115            'internet_reduction' => (int) ($row['internet_reduction'] ?? 0),
116            'multiple_slots_allowed' => (int) ($row['multiple_slots_allowed'] ?? 0),
117            'open_from_days' => (int) ($row['open_from_days'] ?? 0),
118            'open_until_days' => (int) ($row['open_until_days'] ?? 0),
119            'version' => (int) ($row['version'] ?? 1),
120            'changed_by' => $row['changed_by'],
121        ];
122    }
123
124    /**
125     * @param array $data
126     */
127    #[\Override]
128    public function postProcess($data): array
129    {
130        $intFields = [
131            'id',
132            'scopeId',
133            'everyXWeeks',
134            'everyOtherWeek',
135            'workstationCount',
136            'appointmentWorkstationCount',
137            'internetReduction',
138            'multipleSlotsAllowed',
139            'openFromDays',
140            'openUntilDays',
141            'version',
142        ];
143        foreach ($intFields as $field) {
144            $data[$this->getPrefixed($field)] = (int) $data[$this->getPrefixed($field)];
145        }
146
147        $availabilityId = $data[$this->getPrefixed('availabilityId')];
148        $data[$this->getPrefixed('availabilityId')] = $availabilityId !== null ? (int) $availabilityId : null;
149        $data[$this->getPrefixed('weekday')] = Entity::decodeWeekdayMask((int) $data[$this->getPrefixed('weekday')]);
150        $data[$this->getPrefixed('startDate')] = $this->asDateString($data[$this->getPrefixed('startDate')]);
151        $data[$this->getPrefixed('endDate')] = $this->asDateString($data[$this->getPrefixed('endDate')]);
152        $data[$this->getPrefixed('startTime')] = $this->asTimeString($data[$this->getPrefixed('startTime')]);
153        $data[$this->getPrefixed('appointmentStartTime')] = $this->asTimeString(
154            $data[$this->getPrefixed('appointmentStartTime')]
155        );
156        $data[$this->getPrefixed('endTime')] = $this->asTimeString($data[$this->getPrefixed('endTime')]);
157        $data[$this->getPrefixed('appointmentEndTime')] = $this->asTimeString(
158            $data[$this->getPrefixed('appointmentEndTime')]
159        );
160        $data[$this->getPrefixed('timeSlot')] = $this->asTimeString($data[$this->getPrefixed('timeSlot')]);
161
162        return $data;
163    }
164
165    private function asDateString(mixed $value): string
166    {
167        if ($value instanceof \DateTimeInterface) {
168            return $value->format('Y-m-d');
169        }
170
171        $value = (string) $value;
172        if (preg_match('/^\d{4}-\d{2}-\d{2}/', $value) === 1) {
173            return substr($value, 0, 10);
174        }
175
176        return $value;
177    }
178
179    private function asTimeString(mixed $value): string
180    {
181        if ($value instanceof \DateTimeInterface) {
182            return $value->format('H:i:s');
183        }
184
185        $value = (string) $value;
186        if (preg_match('/^\d{1,2}:\d{2}(:\d{2})?/', $value, $matches) === 1) {
187            return $matches[0];
188        }
189
190        return $value;
191    }
192}

Inherited from BO\Zmsbackend\Query\Base

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    }
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    }