onSubstitutionImportEvent()   F
last analyzed

Complexity

Conditions 19
Paths 170

Size

Total Lines 62
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 30
c 0
b 0
f 0
nc 170
nop 1
dl 0
loc 62
rs 3.9333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace App\Notification\EventSubscriber;
4
5
use App\Entity\Exam;
6
use App\Entity\ResourceReservation;
7
use App\Entity\Substitution;
8
use App\Entity\Teacher;
9
use App\Entity\Tuition;
10
use App\Event\SubstitutionImportEvent;
11
use App\Notification\Notification;
12
use App\Notification\NotificationService;
13
use App\Repository\ExamRepositoryInterface;
14
use App\Repository\ResourceReservationRepositoryInterface;
15
use App\Repository\UserRepositoryInterface;
16
use App\Rooms\Reservation\ResourceAvailabilityHelper;
17
use App\Validator\NoReservationCollision;
18
use DateTime;
19
use SchulIT\CommonBundle\Helper\DateHelper;
20
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
21
use Symfony\Component\Mailer\MailerInterface;
22
use Symfony\Component\Mime\Address;
23
use Symfony\Component\Mime\Email;
24
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
25
use Symfony\Component\Validator\ConstraintViolation;
26
use Symfony\Component\Validator\ConstraintViolationInterface;
27
use Symfony\Component\Validator\ConstraintViolationListInterface;
28
use Symfony\Component\Validator\Validator\ValidatorInterface;
29
use Symfony\Contracts\Translation\TranslatorInterface;
30
use Twig\Environment;
31
32
class ReservationCheckerEventSubscriber implements EventSubscriberInterface {
33
34
    public function __construct(private readonly ValidatorInterface $validator, private readonly ResourceReservationRepositoryInterface $reservationRepository,
35
                                private readonly ExamRepositoryInterface $examRepository, private readonly TranslatorInterface $translator,
36
                                private readonly DateHelper $dateHelper, private readonly ResourceAvailabilityHelper $availabilityHelper,
37
                                private readonly UserRepositoryInterface $userRepository, private readonly NotificationService $notificationService,
38
                                private readonly UrlGeneratorInterface $urlGenerator)
39
    {
40
    }
41
42
    public function onSubstitutionImportEvent(SubstitutionImportEvent $event): void {
43
        $substitutions = array_merge($event->getAdded(), $event->getUpdated());
44
        /** @var DateTime $start */
45
        $start = null;
46
47
        /** @var DateTime $end */
48
        $end = null;
49
50
        /** @var Substitution $substitution */
51
        foreach($substitutions as $substitution) {
52
            if($start === null || $substitution->getDate() < $start) {
53
                $start = clone $substitution->getDate();
54
            }
55
56
            if($end === null || $substitution->getDate() > $end) {
57
                $end = clone $substitution->getDate();
58
            }
59
        }
60
61
        if($start === null || $end === null) {
62
            return;
63
        }
64
65
        $today = $this->dateHelper->getToday();
66
67
        while($start <= $end) {
68
            $reservations = $this->reservationRepository->findAllByDate($start);
69
70
            foreach($reservations as $reservation) {
71
                if($reservation->getDate() >= $today) {
72
                    $violations = $this->validator->validate($reservation);
73
74
                    if (count($violations) > 0 && $this->handleViolation($reservation) === false) {
75
                        $this->sendViolationsEmail($reservation, $violations);
76
                    }
77
                }
78
            }
79
80
            $exams = $this->examRepository->findAllByDate($start);
81
82
            foreach($exams as $exam) {
83
                if(!empty($exam->getExternalId())) {
84
                    continue; // Disable check for external exams
85
                }
86
87
                $violations = $this->validator->validate($exam);
88
89
90
                $reservationViolations = [ ];
91
92
                foreach($violations as $violation) {
93
                    if($violation instanceof ConstraintViolation && $violation->getConstraint() instanceof NoReservationCollision) {
94
                        $reservationViolations[] = $violation;
95
                    }
96
                }
97
98
                if(count($reservationViolations) > 0) {
99
                    $this->sendExamViolationsEmail($exam, $reservationViolations);
100
                }
101
            }
102
103
            $start->modify('+1 day');
104
        }
105
    }
106
107
    private function handleViolation(ResourceReservation $reservation): bool {
108
        $conflictingSubstitutions = 0;
109
110
        for($lesson = $reservation->getLessonStart(); $lesson <= $reservation->getLessonEnd(); $lesson++) {
111
            $availability = $this->availabilityHelper->getAvailability($reservation->getResource(), $reservation->getDate(), $lesson);
0 ignored issues
show
Bug introduced by
It seems like $reservation->getDate() can also be of type null; however, parameter $date of App\Rooms\Reservation\Re...lper::getAvailability() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
            $availability = $this->availabilityHelper->getAvailability($reservation->getResource(), /** @scrutinizer ignore-type */ $reservation->getDate(), $lesson);
Loading history...
Bug introduced by
It seems like $reservation->getResource() can also be of type null; however, parameter $resource of App\Rooms\Reservation\Re...lper::getAvailability() does only seem to accept App\Entity\ResourceEntity, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
            $availability = $this->availabilityHelper->getAvailability(/** @scrutinizer ignore-type */ $reservation->getResource(), $reservation->getDate(), $lesson);
Loading history...
112
113
            if($availability === null) {
114
                continue;
115
            }
116
117
            if(($availability->getTimetableLesson() !== null && !$availability->isTimetableLessonCancelled()) || count($availability->getExams()) > 0) {
118
                return false;
119
            }
120
121
            $substitution = $availability->getSubstitution();
122
            if($substitution !== null) {
123
                $teachers = $substitution->getTeachers()->map(fn(Teacher $teacher) => $teacher->getId())->toArray();
124
                $replacementTeachers = $substitution->getReplacementTeachers()->map(fn(Teacher $teacher) => $teacher->getId())->toArray();
125
126
                if (!(count($replacementTeachers) > 0 && in_array($reservation->getTeacher()->getId(), $replacementTeachers) || count($replacementTeachers) === 0 && in_array($reservation->getTeacher()->getId(), $teachers))) {
127
                    $conflictingSubstitutions++;
128
                }
129
            }
130
        }
131
132
        if($conflictingSubstitutions === 0) {
0 ignored issues
show
introduced by
The condition $conflictingSubstitutions === 0 is always true.
Loading history...
133
            $this->sendReservationRemovedEmail($reservation);
134
            $this->reservationRepository->remove($reservation);
135
136
            return true;
137
        }
138
139
        return false;
140
    }
141
142
    private function sendReservationRemovedEmail(ResourceReservation $reservation): void {
143
        if($reservation->getTeacher() === null) {
144
            return;
145
        }
146
147
        foreach($this->userRepository->findAllTeachers([$reservation->getTeacher()]) as $recipient) {
148
            $notification = new Notification(
149
                $recipient,
150
                $this->translator->trans('reservation_removed.title', [], 'email'),
151
                $this->translator->trans('reservation_removed.content', [
152
                    '%resource%' => $reservation->getResource()->getName(),
153
                    '%date%' => $reservation->getDate()->format($this->translator->trans('date.format_short')),
154
                    '%lesson%' => $this->translator->trans('label.substitution_lessons', [
155
                        '%start%' => $reservation->getLessonStart(),
156
                        '%end%' => $reservation->getLessonEnd(),
157
                        '%count%' => ($reservation->getLessonEnd() - $reservation->getLessonStart())
158
                    ])
159
                ], 'email'),
160
                $this->urlGenerator->generate('substitutions', [], UrlGeneratorInterface::ABSOLUTE_URL),
161
                $this->translator->trans('reservation_removed.link', [], 'email')
162
            );
163
164
            $this->notificationService->notify($notification);
165
        }
166
    }
167
168
    private function sendViolationsEmail(ResourceReservation $reservation, ConstraintViolationListInterface $violationList): void {
0 ignored issues
show
Unused Code introduced by
The parameter $violationList is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

168
    private function sendViolationsEmail(ResourceReservation $reservation, /** @scrutinizer ignore-unused */ ConstraintViolationListInterface $violationList): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
169
        if($reservation->getTeacher() === null) {
170
            return;
171
        }
172
173
        foreach($this->userRepository->findAllTeachers([$reservation->getTeacher()]) as $recipient) {
174
            $notification = new Notification(
175
                $recipient,
176
                $this->translator->trans('reservation.title', [], 'email'),
177
                $this->translator->trans('reservation.content', [
178
                    '%room%' => $reservation->getResource()->getName(),
179
                    '%date%' => $reservation->getDate()->format($this->translator->trans('date.format_short')),
180
                    '%lesson%' => $this->translator->trans('label.substitution_lessons', [
181
                        '%start%' => $reservation->getLessonStart(),
182
                        '%end%' => $reservation->getLessonEnd(),
183
                        '%count%' => ($reservation->getLessonEnd() - $reservation->getLessonStart())
184
                    ])
185
                ], 'email'),
186
                $this->urlGenerator->generate('edit_room_reservation', ['uuid' => $reservation->getUuid()->toString()], UrlGeneratorInterface::ABSOLUTE_URL),
187
                $this->translator->trans('reservation.link', [], 'email')
188
            );
189
190
            $this->notificationService->notify($notification);
191
        }
192
    }
193
194
    /**
195
     * @param ConstraintViolationInterface[] $violationList
196
     */
197
    private function sendExamViolationsEmail(Exam $exam, array $violationList): void {
0 ignored issues
show
Unused Code introduced by
The parameter $violationList is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

197
    private function sendExamViolationsEmail(Exam $exam, /** @scrutinizer ignore-unused */ array $violationList): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
198
        $teachers = [ ];
199
200
        /** @var Tuition $tuition */
201
        foreach($exam->getTuitions() as $tuition) {
202
            foreach($tuition->getTeachers() as $teacher) {
203
                if(!in_array($teacher, $teachers)) {
204
                    $teachers[] = $teacher;
205
                }
206
            }
207
        }
208
209
        foreach($this->userRepository->findAllTeachers($teachers) as $recipient) {
210
            $notification = new Notification(
211
                $recipient,
212
                $this->translator->trans('reservation.title', [], 'email'),
213
                $this->translator->trans('reservation.content_exam', [
214
                    '%room%' => $exam->getRoom()?->getName(),
215
                    '%date%' => $exam->getDate()->format($this->translator->trans('date.format_short')),
216
                    '%lesson%' => $this->translator->trans('label.substitution_lessons', [
217
                        '%start%' => $exam->getLessonStart(),
218
                        '%end%' => $exam->getLessonEnd(),
219
                        '%count%' => ($exam->getLessonEnd() - $exam->getLessonStart())
220
                    ])
221
                ], 'email'),
222
                $this->urlGenerator->generate('edit_exam', ['uuid' => $exam->getUuid()->toString()], UrlGeneratorInterface::ABSOLUTE_URL),
223
                $this->translator->trans('reservation.link', [], 'email')
224
            );
225
226
            $this->notificationService->notify($notification);
227
        }
228
    }
229
230
    /**
231
     * @inheritDoc
232
     */
233
    public static function getSubscribedEvents(): array {
234
        return [
235
            SubstitutionImportEvent::class => 'onSubstitutionImportEvent'
236
        ];
237
    }
238
}