Passed
Push — master ( 57cb8f...650c8b )
by Marcel
07:27
created

TimetableLessonRepository::findAllByDate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace App\Repository;
4
5
use App\Entity\Grade;
6
use App\Entity\Room;
7
use App\Entity\Student;
8
use App\Entity\Subject;
9
use App\Entity\Teacher;
10
use App\Entity\TimetableLesson;
11
use App\Entity\Tuition;
12
use DateTime;
13
use Doctrine\ORM\QueryBuilder;
14
use Doctrine\ORM\Tools\Pagination\Paginator;
15
16
class TimetableLessonRepository extends AbstractTransactionalRepository implements TimetableLessonRepositoryInterface {
17
18
    private function getDefaultQueryBuilder(?DateTime $start = null, ?DateTime $end = null): QueryBuilder {
19
        $qb = $this->em->createQueryBuilder()
20
            ->select(['l', 'r', 't', 'sg', 'g']) // do not hydrate the tuition as it may be null and get hydrated (https://github.com/doctrine/orm/issues/8446)
21
            ->from(TimetableLesson::class, 'l')
22
            ->leftJoin('l.tuition', 't')
23
            ->leftJoin('t.studyGroup', 'sg')
24
            ->leftJoin('sg.grades', 'g')
25
            ->leftJoin('l.room', 'r')
26
            ->leftJoin('l.subject', 's');
27
28
        if($start !== null) {
29
            $qb->andWhere('l.date >= :start')
30
                ->setParameter('start', $start);
31
        }
32
33
        if($end !== null) {
34
            $qb->andWhere('l.date <= :end')
35
                ->setParameter('end', $end);
36
        }
37
38
        return $qb;
39
    }
40
41
    /**
42
     * @inheritDoc
43
     */
44
    public function findOneById(int $id): ?TimetableLesson {
45
        return $this->getDefaultQueryBuilder()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getDefault...)->getOneOrNullResult() could return the type integer which is incompatible with the type-hinted return App\Entity\TimetableLesson|null. Consider adding an additional type-check to rule them out.
Loading history...
46
            ->where('l.id = :id')
47
            ->setParameter('id', $id)
48
            ->getQuery()
49
            ->getOneOrNullResult();
50
    }
51
52
    public function findOneByUuid(string $uuid): ?TimetableLesson {
53
        return $this->getDefaultQueryBuilder()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getDefault...)->getOneOrNullResult() could return the type integer which is incompatible with the type-hinted return App\Entity\TimetableLesson|null. Consider adding an additional type-check to rule them out.
Loading history...
54
            ->where('l.uuid = :uuid')
55
            ->setParameter('uuid', $uuid)
56
            ->getQuery()
57
            ->getOneOrNullResult();
58
    }
59
60
    /**
61
     * @inheritDoc
62
     */
63
    public function persist(TimetableLesson $lesson): void {
64
        $this->em->persist($lesson);
65
        $this->flushIfNotInTransaction();
66
    }
67
68
    /**
69
     * @inheritDoc
70
     */
71
    public function remove(TimetableLesson $lesson): void {
72
        $this->em->remove($lesson);
73
        $this->flushIfNotInTransaction();
74
    }
75
76
    public function removeRange(DateTime $start, DateTime $end): void {
77
        $this->em->createQueryBuilder()
78
            ->delete(TimetableLesson::class, 'l')
79
            ->where('l.date >= :start')
80
            ->andWhere('l.date <= :end')
81
            ->setParameter('start', $start)
82
            ->setParameter('end', $end)
83
            ->getQuery()
84
            ->execute();
85
        $this->flushIfNotInTransaction();
86
    }
87
88
    /**
89
     * @inheritDoc
90
     */
91
    public function findAllByGrade(DateTime $start, DateTime $end, Grade $grade): array {
92
        $qb = $this->getDefaultQueryBuilder($start, $end);
93
94
        $qbInner = $this->em->createQueryBuilder()
95
            ->select('lInner')
96
            ->from(TimetableLesson::class, 'lInner')
97
            ->leftJoin('lInner.grades', 'gInner')
98
            ->where('gInner.id = :grade');
99
100
        $qb->setParameter('grade', $grade->getId());
101
102
        $qb->andWhere(
103
            $qb->expr()->in('l.id', $qbInner->getDQL())
104
        );
105
106
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
107
    }
108
109
    /**
110
     * @inheritDoc
111
     */
112
    public function findAllByTeacher(DateTime $start, DateTime $end, Teacher $teacher): array {
113
        $qb = $this->getDefaultQueryBuilder($start, $end);
114
115
        $qbInner = $this->em->createQueryBuilder()
116
            ->select('tInner.id')
117
            ->from(TimetableLesson::class, 'tInner')
118
            ->leftJoin('tInner.teachers', 'teacherInner')
119
            ->where('teacherInner.id = :teacher');
120
121
        $qb->andWhere(
122
            $qb->expr()->in('l.id', $qbInner->getDQL())
123
        )
124
            ->setParameter('teacher', $teacher->getId());
125
126
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
127
    }
128
129
    /**
130
     * @inheritDoc
131
     */
132
    public function findAllByRoom(DateTime $start, DateTime $end, Room $room): array {
133
        $qb = $this->getDefaultQueryBuilder($start, $end);
134
135
        $qbInner = $this->em->createQueryBuilder()
136
            ->select('lInner.id')
137
            ->from(TimetableLesson::class, 'lInner')
138
            ->leftJoin('lInner.room', 'rInner')
139
            ->where('rInner.id = :room')
140
            ->setParameter('room', $room->getId());
141
142
        $qb->andWhere(
143
            $qb->expr()->in('l.id', $qbInner->getDQL())
144
        );
145
146
        $qb->setParameter('room', $room->getId());
147
148
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
149
    }
150
151
    /**
152
     * @inheritDoc
153
     */
154
    public function findAllByStudent(DateTime $start, DateTime $end, Student $student): array {
155
        $qb = $this->getDefaultQueryBuilder($start, $end);
156
157
        $qbInner = $this->em->createQueryBuilder()
158
            ->select('lInner')
159
            ->from(TimetableLesson::class, 'lInner')
160
            ->leftJoin('lInner.tuition', 'tInner')
161
            ->leftJoin('tInner.studyGroup', 'sgInner')
162
            ->leftJoin('sgInner.memberships', 'sgmInner')
163
            ->leftJoin('sgmInner.student', 'studentInner')
164
            ->where('studentInner.id = :student');
165
166
        $qb->setParameter('student', $student->getId());
167
168
        $qb->andWhere(
169
            $qb->expr()->in('l.id', $qbInner->getDQL())
170
        );
171
172
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
173
    }
174
175
    /**
176
     * @inheritDoc
177
     */
178
    public function findAllBySubjects(DateTime $start, DateTime $end, array $subjects): array {
179
        $qb = $this->getDefaultQueryBuilder($start, $end);
180
181
        $qbInner = $this->em->createQueryBuilder()
182
            ->select('lInner')
183
            ->from(TimetableLesson::class, 'lInner')
184
            ->leftJoin('lInner.tuition', 'tInner')
185
            ->leftJoin('tInner.subject', 'sInner')
186
            ->leftJoin('lInner.subject', 'lsInner')
187
            ->andWhere(
188
                $qb->expr()->orX(
189
                    $qb->expr()->in('sInner.id', ':subjects'),
190
                    $qb->expr()->in('lsInner.id', ':subjects')
191
                )
192
            );
193
194
        $subjectIds = array_map(fn(Subject $subject) => $subject->getId(), $subjects);
195
196
        $qb->setParameter('subjects', $subjectIds);
197
198
        $qb->andWhere(
199
            $qb->expr()->in('l.id', $qbInner->getDQL())
200
        );
201
202
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
203
    }
204
205
    /**
206
     * @inheritDoc
207
     */
208
    public function findAllByTuitions(DateTime $start, DateTime $end, array $tuitions): array {
209
        $ids = array_map(fn(Tuition $tuition) => $tuition->getId(), $tuitions);
210
211
        $qb = $this->getDefaultQueryBuilder($start, $end);
212
213
        $qbInner = $this->em->createQueryBuilder()
214
            ->select('lInner')
215
            ->from(TimetableLesson::class, 'lInner')
216
            ->leftJoin('lInner.tuition', 'tInner')
217
            ->where($qb->expr()->in('tInner.id', ':tuitions'));
218
219
        $qb->setParameter('tuitions', $ids);
220
221
        $qb->andWhere(
222
            $qb->expr()->in('l.id', $qbInner->getDQL())
223
        );
224
225
        return $qb->getQuery()->getResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
226
    }
227
228
    /**
229
     * @inheritDoc
230
     */
231
    public function findOneByDateAndRoomAndLesson(DateTime $date, Room $room, int $lessonNumber): ?TimetableLesson {
232
        $qb = $this->getDefaultQueryBuilder($date, $date);
233
234
        $qbInner = $this->em->createQueryBuilder()
235
            ->select('lInner.id')
236
            ->from(TimetableLesson::class, 'lInner')
237
            ->leftJoin('lInner.room', 'rInner')
238
            ->where('lInner.lessonStart <= :lesson')
239
            ->andWhere('lInner.lessonEnd >= :lesson')
240
            ->andWhere('rInner.id = :room');
241
        $qb
242
            ->setParameter('room', $room->getId())
243
            ->setParameter('lesson', $lessonNumber);
244
245
        $qb->andWhere(
246
            $qb->expr()->in('l.id', $qbInner->getDQL())
247
        )
248
            ->setMaxResults(1);
249
250
        return $qb->getQuery()->getOneOrNullResult();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb->getQuery()->getOneOrNullResult() could return the type integer which is incompatible with the type-hinted return App\Entity\TimetableLesson|null. Consider adding an additional type-check to rule them out.
Loading history...
251
    }
252
253
    /**
254
     * @inheritDoc
255
     */
256
    public function findAllByRange(DateTime $start, DateTime $end): array {
257
        return $this->getDefaultQueryBuilder($start, $end)
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getDefault...getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
258
            ->getQuery()
259
            ->getResult();
260
    }
261
262
    /**
263
     * @inheritDoc
264
     */
265
    public function findAll(): array {
266
        return $this->em->getRepository(TimetableLesson::class)
267
            ->findAll();
268
    }
269
270
    /**
271
     * @inheritDoc
272
     */
273
    public function removeStartingFrom(DateTime $dateTime): int {
274
        return $this->em->createQueryBuilder()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->em->create...->getQuery()->execute() could return the type array<mixed,mixed> which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
275
            ->delete(TimetableLesson::class, 'l')
276
            ->where('l.date >= :date')
277
            ->setParameter('date', $dateTime)
278
            ->getQuery()
279
            ->execute();
280
    }
281
282
    private function getMissingQueryBuilder(): QueryBuilder {
283
        $qbInner = $this->em->createQueryBuilder()
284
            ->select('lIntern.id')
285
            ->from(TimetableLesson::class, 'lIntern')
286
            ->leftJoin('lIntern.entries', 'eIntern')
287
            ->leftJoin('lIntern.tuition', 'tIntern')
288
            ->where('eIntern.id IS NOT NULL')
289
            ->andWhere('tIntern.isBookEnabled = true')
290
            ->groupBy('lIntern.id, lIntern.lessonEnd, lIntern.lessonStart')
291
            ->having('SUM(eIntern.lessonEnd - eIntern.lessonStart + 1) != (lIntern.lessonEnd - lIntern.lessonStart + 1)');
292
293
        $qb = $this->em->createQueryBuilder();
294
295
        $qb->select(['l', 'e', 't'])
296
            ->from(TimetableLesson::class, 'l')
297
            ->leftJoin('l.entries', 'e')
298
            ->leftJoin('l.tuition', 't')
299
            ->where('t.isBookEnabled = true')
300
            ->andWhere(
301
                $qb->expr()->orX(
302
                    $qb->expr()->isNull('e.id'),                    // lessons without any entries
303
                    $qb->expr()->in('l.id', $qbInner->getDQL())     // partly incomplete lessons
304
                )
305
            );
306
307
        return $qb;
308
    }
309
310
    private function getMissingByTeacherQueryBuilder(Teacher $teacher, DateTime $start, DateTime $end): QueryBuilder {
311
        $qb = $this->getMissingQueryBuilder();
312
313
        $qbInner = $this->em->createQueryBuilder()
314
            ->select('lInner.id')
315
            ->from(TimetableLesson::class, 'lInner')
316
            ->where('lInner.date >= :start')
317
            ->andWhere('lInner.date <= :end')
318
            ->leftJoin('lInner.tuition', 'tInner')
319
            ->leftJoin('tInner.teachers', 'ttInner');
320
        $qbInner
321
            ->andWhere(
322
                'ttInner.id = :teacher'
323
            );
324
325
        $qb->andWhere(
326
            $qb->expr()->in('l.id', $qbInner->getDQL())
327
        )
328
            ->setParameter('start', $start)
329
            ->setParameter('end', $end)
330
            ->setParameter('teacher', $teacher->getId());
331
332
        return $qb;
333
    }
334
335
    public function getMissingByTeacherPaginator(int $itemsPerPage, int &$page, Teacher $teacher, DateTime $start, DateTime $end): Paginator {
336
        if($page < 1) {
337
            $page = 1;
338
        }
339
340
        $offset = ($page - 1) * $itemsPerPage;
341
342
        $paginator = new Paginator($this->getMissingByTeacherQueryBuilder($teacher, $start, $end)->orderBy('l.date', 'desc'));
343
        $paginator->getQuery()
344
            ->setMaxResults($itemsPerPage)
345
            ->setFirstResult($offset);
346
347
        return $paginator;
348
    }
349
350
    private function getMissingByGradeQueryBuilder(Grade $grade, DateTime $start, DateTime $end): QueryBuilder {
351
        $qb = $this->getMissingQueryBuilder();
352
353
        $qbInner = $this->em->createQueryBuilder()
354
            ->select('lInner.id')
355
            ->from(TimetableLesson::class, 'lInner')
356
            ->where('lInner.date >= :start')
357
            ->andWhere('lInner.date <= :end')
358
            ->leftJoin('lInner.tuition', 'tInner')
359
            ->leftJoin('tInner.studyGroup', 'sgInner')
360
            ->leftJoin('sgInner.grades', 'gInner')
361
            ->andWhere('gInner.id = :grade');
362
363
        $qb->andWhere(
364
            $qb->expr()->in('l.id', $qbInner->getDQL())
365
        )
366
            ->setParameter('start', $start)
367
            ->setParameter('end', $end)
368
            ->setParameter('grade', $grade->getId());
369
370
        return $qb;
371
    }
372
373
    public function getMissingByGradePaginator(int $itemsPerPage, int &$page, Grade $grade, DateTime $start, DateTime $end): Paginator {
374
        if($page < 1) {
375
            $page = 1;
376
        }
377
378
        $offset = ($page - 1) * $itemsPerPage;
379
380
        $paginator = new Paginator($this->getMissingByGradeQueryBuilder($grade, $start, $end)->orderBy('l.date', 'desc'));
381
        $paginator->getQuery()
382
            ->setMaxResults($itemsPerPage)
383
            ->setFirstResult($offset);
384
385
        return $paginator;
386
    }
387
388
    private function getMissingByTuitionQueryBuilder(Tuition $tuition, DateTime $start, DateTime $end): QueryBuilder {
389
        $qb = $this->getMissingQueryBuilder();
390
391
        $qbInner = $this->em->createQueryBuilder()
392
            ->select('lInner.id')
393
            ->from(TimetableLesson::class, 'lInner')
394
            ->where('lInner.date >= :start')
395
            ->andWhere('lInner.date <= :end')
396
            ->leftJoin('lInner.tuition', 'tInner')
397
            ->andWhere('tInner.id = :tuition');
398
399
        $qb->andWhere(
400
            $qb->expr()->in('l.id', $qbInner->getDQL())
401
        )
402
            ->setParameter('start', $start)
403
            ->setParameter('end', $end)
404
            ->setParameter('tuition', $tuition->getId());
405
406
        return $qb;
407
    }
408
409
    public function getMissingByTuitionPaginator(int $itemsPerPage, int &$page, Tuition $tuition, DateTime $start, DateTime $end): Paginator {
410
        if($page < 1) {
411
            $page = 1;
412
        }
413
414
        $offset = ($page - 1) * $itemsPerPage;
415
416
        $paginator = new Paginator($this->getMissingByTuitionQueryBuilder($tuition, $start, $end)->orderBy('l.date', 'desc'));
417
        $paginator->getQuery()
418
            ->setMaxResults($itemsPerPage)
419
            ->setFirstResult($offset);
420
421
        return $paginator;
422
    }
423
424
    public function countMissingByGrade(Grade $grade, DateTime $start, DateTime $end): int {
425
        return $this->getMissingByGradeQueryBuilder($grade, $start, $end)
426
            ->select('COUNT(DISTINCT l.id)')
427
            ->getQuery()
428
            ->getSingleScalarResult();
429
    }
430
431
    public function countMissingByTeacher(Teacher $teacher, DateTime $start, DateTime $end): int {
432
        return $this->getMissingByTeacherQueryBuilder($teacher, $start, $end)
433
            ->select('COUNT(DISTINCT l.id)')
434
            ->getQuery()
435
            ->getSingleScalarResult();
436
    }
437
438
    public function countMissingByTuition(Tuition $tuition, DateTime $start, DateTime $end): int {
439
        return $this->getMissingByTuitionQueryBuilder($tuition, $start, $end)
440
            ->select('COUNT(DISTINCT l.id)')
441
            ->getQuery()
442
            ->getSingleScalarResult();
443
    }
444
445
    public function countHoldLessons(array $tuitions, ?Student $student): int {
446
        $tuitionIds = array_map(fn(Tuition $tuition) => $tuition->getId(), $tuitions);
447
448
        $qb = $this->em->createQueryBuilder()
449
            ->select('SUM(l.lessonEnd - l.lessonStart + 1)')
450
            ->from(TimetableLesson::class, 'l')
451
            ->leftJoin('l.tuition', 't')
452
            ->leftJoin('l.entries', 'e')
453
            ->where('t.id IN (:tuitions)')
454
            ->setParameter('tuitions', $tuitionIds);
455
456
        if($student !== null) {
457
            $qb->leftJoin('t.studyGroup', 'sg')
458
                ->leftJoin('sg.memberships', 'm')
459
                ->leftJoin('m.student', 's')
460
                ->leftJoin('e.attendances', 'a')
461
                ->andWhere('a.student = :student')
462
                ->andWhere('s.id = :student')
463
                ->setParameter('student', $student->getId());
464
        }
465
466
        return (int)$qb->getQuery()
467
            ->getSingleScalarResult();
468
    }
469
470
    public function findAllByDate(DateTime $dateTime): array {
471
        return $this->getDefaultQueryBuilder($dateTime, $dateTime)
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getDefault...getQuery()->getResult() could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
472
            ->getQuery()
473
            ->getResult();
474
    }
475
476
}