ListController   B
last analyzed

Complexity

Total Complexity 50

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 185
c 1
b 0
f 0
dl 0
loc 327
rs 8.4
ccs 0
cts 153
cp 0
wmc 50

12 Methods

Rating   Name   Duplication   Size   Complexity  
A filterImplicitTeacherTag() 0 14 6
B privacy() 0 40 6
C tuitions() 0 56 13
A tuition() 0 30 3
A lms() 0 24 2
A teachers() 0 24 3
C studyGroups() 0 83 12
A __construct() 0 4 1
A getMessageScope() 0 2 1
A exportTuition() 0 5 1
A exportLms() 0 5 1
A exportStudyGroup() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like ListController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ListController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace App\Controller;
4
5
use App\Entity\LearningManagementSystem;
6
use App\Export\LearningManagementSystemInfoCsvExporter;
7
use App\Repository\StudyGroupMembershipRepositoryInterface;
8
use App\View\Filter\LearningManagementSystemFilter;
9
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
10
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
11
use Symfony\Component\HttpFoundation\Response;
12
use App\Entity\Exam;
13
use App\Entity\Grade;
14
use App\Entity\GradeTeacher;
15
use App\Entity\GradeTeacherType;
16
use App\Entity\MessageScope;
17
use App\Entity\PrivacyCategory;
18
use App\Entity\Student;
19
use App\Entity\StudyGroup;
20
use App\Entity\StudyGroupMembership;
21
use App\Entity\StudyGroupType;
22
use App\Entity\Teacher;
23
use App\Entity\TeacherTag;
24
use App\Entity\Tuition;
25
use App\Entity\User;
26
use App\Export\StudyGroupCsvExporter;
27
use App\Export\TuitionCsvExporter;
28
use App\Grouping\Grouper;
29
use App\Grouping\TeacherFirstCharacterStrategy;
30
use App\Message\DismissedMessagesHelper;
31
use App\Repository\ExamRepositoryInterface;
32
use App\Repository\ImportDateTypeRepositoryInterface;
33
use App\Repository\MessageRepositoryInterface;
34
use App\Repository\PrivacyCategoryRepositoryInterface;
35
use App\Repository\StudentRepositoryInterface;
36
use App\Repository\TeacherRepositoryInterface;
37
use App\Repository\TuitionRepositoryInterface;
38
use App\Section\SectionResolverInterface;
39
use App\Security\Voter\ExamVoter;
40
use App\Security\Voter\ListsVoter;
41
use App\Sorting\Sorter;
42
use App\Sorting\StudentGroupMembershipStrategy;
43
use App\Sorting\StudentStrategy;
44
use App\Sorting\StudyGroupStrategy;
45
use App\Sorting\TeacherFirstCharacterGroupStrategy;
46
use App\Sorting\TeacherStrategy;
47
use App\Sorting\TuitionStrategy;
48
use App\View\Filter\GradeFilter;
49
use App\View\Filter\SectionFilter;
50
use App\View\Filter\StudentFilter;
51
use App\View\Filter\StudyGroupFilter;
52
use App\View\Filter\SubjectFilter;
53
use App\View\Filter\TeacherFilter;
54
use App\View\Filter\TeacherTagFilter;
55
use SchulIT\CommonBundle\Helper\DateHelper;
56
use SchulIT\CommonBundle\Utils\RefererHelper;
57
use Symfony\Component\HttpFoundation\Request;
58
use Symfony\Component\Routing\Annotation\Route;
59
60
class ListController extends AbstractControllerWithMessages {
61
62
    public function __construct(private Grouper $grouper, private Sorter $sorter, private ImportDateTypeRepositoryInterface $importDateTimeRepository,
63
                                MessageRepositoryInterface $messageRepository, DismissedMessagesHelper $dismissedMessagesHelper,
64
                                DateHelper $dateHelper, RefererHelper $refererHelper) {
65
        parent::__construct($messageRepository, $dismissedMessagesHelper, $dateHelper, $refererHelper);
66
    }
67
68
    protected function getMessageScope(): MessageScope {
69
        return MessageScope::Lists;
70
    }
71
72
    #[Route(path: '/tuitions', name: 'list_tuitions')]
73
    public function tuitions(SectionFilter $sectionFilter, GradeFilter $gradeFilter, StudentFilter $studentFilter, TeacherFilter $teacherFilter, TuitionRepositoryInterface $tuitionRepository, Request $request): Response {
74
        $this->denyAccessUnlessGranted(ListsVoter::Tuitions);
75
76
        /** @var User $user */
77
        $user = $this->getUser();
78
79
        $sectionFilterView = $sectionFilter->handle($request->query->get('section'));
80
        $gradeFilterView = $gradeFilter->handle($request->query->get('grade', null), $sectionFilterView->getCurrentSection(), $user);
81
        $studentFilterView = $studentFilter->handle($request->query->get('student', null), $sectionFilterView->getCurrentSection(), $user);
82
        $teacherFilterView = $teacherFilter->handle($request->query->get('teacher', null), $sectionFilterView->getCurrentSection(), $user, $gradeFilterView->getCurrentGrade() === null && $studentFilterView->getCurrentStudent() === null);
83
84
        $tuitions = [ ];
85
        $memberships = [ ];
86
        $teacherMailAddresses = [ ];
87
88
        if($studentFilterView->getCurrentStudent() !== null && $sectionFilterView->getCurrentSection() !== null) {
89
            $tuitions = $tuitionRepository->findAllByStudents([$studentFilterView->getCurrentStudent()], $sectionFilterView->getCurrentSection());
90
91
            foreach($tuitions as $tuition) {
92
                /** @var StudyGroupMembership|null $membership */
93
                $membership = $tuition->getStudyGroup()->getMemberships()->filter(fn(StudyGroupMembership $membership) => $membership->getStudent()->getId() === $studentFilterView->getCurrentStudent()->getId())->first();
94
95
                $memberships[$tuition->getExternalId()] = $membership->getType();
96
            }
97
        } else if($gradeFilterView->getCurrentGrade() !== null) {
98
            $tuitions = $tuitionRepository->findAllByGrades([$gradeFilterView->getCurrentGrade()], $sectionFilterView->getCurrentSection());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection() can also be of type null; however, parameter $section of App\Repository\TuitionRe...face::findAllByGrades() does only seem to accept App\Entity\Section, 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

98
            $tuitions = $tuitionRepository->findAllByGrades([$gradeFilterView->getCurrentGrade()], /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection());
Loading history...
99
        } else if($teacherFilterView->getCurrentTeacher() !== null && $sectionFilterView->getCurrentSection() !== null) {
100
            $tuitions = $tuitionRepository->findAllByTeacher($teacherFilterView->getCurrentTeacher(), $sectionFilterView->getCurrentSection());
101
        }
102
103
        if($studentFilterView->getCurrentStudent() !== null || $gradeFilterView->getCurrentGrade() !== null) {
104
            foreach($tuitions as $tuition) {
105
                foreach($tuition->getTeachers() as $teacher) {
106
                    if($teacher->getEmail() !== null) {
107
                        $teacherMailAddresses[] = $teacher->getEmail();
108
                    }
109
                }
110
            }
111
112
            $teacherMailAddresses = array_unique($teacherMailAddresses);
113
        }
114
115
        $tuitions = array_filter($tuitions, fn(Tuition $tuition) => $tuition->getSection() === $sectionFilterView->getCurrentSection());
116
117
        $this->sorter->sort($tuitions, TuitionStrategy::class);
118
119
        return $this->renderWithMessages('lists/tuitions.html.twig', [
120
            'sectionFilter' => $sectionFilterView,
121
            'gradeFilter' => $gradeFilterView,
122
            'studentFilter' => $studentFilterView,
123
            'teacherFilter' => $teacherFilterView,
124
            'tuitions' => $tuitions,
125
            'memberships' => $memberships,
126
            'teacherMailAddresses' => $teacherMailAddresses,
127
            'last_import' => $this->importDateTimeRepository->findOneByEntityClass(Tuition::class)
128
        ]);
129
    }
130
131
    #[Route(path: '/tuitions/{uuid}', name: 'list_tuition')]
132
    public function tuition(Tuition $tuition, TuitionRepositoryInterface $tuitionRepository, ExamRepositoryInterface $examRepository): Response {
133
        $this->denyAccessUnlessGranted(ListsVoter::Tuitions);
134
135
        $tuition = $tuitionRepository->findOneById($tuition->getId());
136
        /** @var StudyGroupMembership[] $memberships */
137
        $memberships = $tuition->getStudyGroup()->getMemberships()->toArray();
138
        $this->sorter->sort($memberships, StudentGroupMembershipStrategy::class);
139
140
        $exams = $examRepository->findAllByTuitions([$tuition], null, true);
141
142
        $exams = array_filter($exams, fn(Exam $exam) => $this->isGranted(ExamVoter::Show, $exam));
143
144
        $types = [ ];
145
146
        foreach($memberships as $membership) {
147
            if(!array_key_exists($membership->getType(), $types)) {
148
                $types[$membership->getType()] = 0;
149
            }
150
151
            $types[$membership->getType()]++;
152
        }
153
154
        return $this->renderWithMessages('lists/tuition.html.twig', [
155
            'tuition' => $tuition,
156
            'memberships' => $memberships,
157
            'exams' => $exams,
158
            'today' => $this->dateHelper->getToday(),
159
            'last_import' => $this->importDateTimeRepository->findOneByEntityClass(Tuition::class),
160
            'types' => $types
161
        ]);
162
    }
163
164
    #[Route(path: '/tuitions/{uuid}/export', name: 'export_tuition')]
165
    public function exportTuition(Tuition $tuition, TuitionCsvExporter $tuitionCsvExporter): Response {
166
        $this->denyAccessUnlessGranted(ListsVoter::Tuitions);
167
168
        return $tuitionCsvExporter->getCsvResponse($tuition);
169
    }
170
171
    #[Route(path: '/study_groups', name: 'list_studygroups')]
172
    public function studyGroups(SectionFilter $sectionFilter, StudyGroupFilter $studyGroupFilter, StudentFilter $studentFilter,
173
                                TuitionRepositoryInterface $tuitionRepository, Request $request, Sorter $sorter): Response {
174
        $this->denyAccessUnlessGranted(ListsVoter::StudyGroups);
175
176
        /** @var User $user */
177
        $user = $this->getUser();
178
179
        $sectionFilterView = $sectionFilter->handle($request->query->get('section'));
180
        $studyGroupFilterView = $studyGroupFilter->handle($request->query->get('study_group', null), $sectionFilterView->getCurrentSection(), $user);
181
        $studentFilterView = $studentFilter->handle($request->query->get('student', null), $sectionFilterView->getCurrentSection(), $user);
182
183
        $students = [ ];
184
        $studyGroups = [ ];
185
        $memberships = [ ];
186
187
        if($studyGroupFilterView->getCurrentStudyGroup() !== null) {
188
            /** @var StudyGroupMembership $membership */
189
            foreach($studyGroupFilterView->getCurrentStudyGroup()->getMemberships() as $membership) {
190
                $students[] = $membership->getStudent();
191
                $memberships[$membership->getStudent()->getId()] = $membership->getType();
192
            }
193
194
            $this->sorter->sort($students, StudentStrategy::class);
195
        } else if($studentFilterView->getCurrentStudent() !== null) {
196
            $studyGroups = [ ];
197
            $memberships = [ ];
198
199
            /** @var StudyGroupMembership $membership */
200
            foreach($studentFilterView->getCurrentStudent()->getStudyGroupMemberships() as $membership) {
201
                $studyGroups[] = $membership->getStudyGroup();
202
                $memberships[$membership->getStudyGroup()->getId()] = $membership->getType();
203
            }
204
205
            $this->sorter->sort($studyGroups, StudyGroupStrategy::class);
206
        }
207
208
        $studyGroups = array_filter($studyGroups, fn(StudyGroup $studyGroup) => $studyGroup->getSection() === $sectionFilterView->getCurrentSection());
209
210
        $grade = null;
211
        $gradeTeachers = [ ];
212
        $substitutionalGradeTeachers = [ ];
213
214
        if($studyGroupFilterView->getCurrentStudyGroup() !== null && $studyGroupFilterView->getCurrentStudyGroup()->getType() === StudyGroupType::Grade) {
215
            /** @var Grade $grade */
216
            $grade = $studyGroupFilterView->getCurrentStudyGroup()->getGrades()->first();
217
        } else if($studentFilterView->getCurrentStudent() !== null && $sectionFilterView->getCurrentSection() !== null) {
218
            $grade = $studentFilterView->getCurrentStudent()->getGrade($sectionFilterView->getCurrentSection());
219
        }
220
221
        if($grade !== null) {
222
            $gradeTeachers = array_map(fn(GradeTeacher $gradeTeacher) => $gradeTeacher->getTeacher(), array_filter($grade->getTeachers()->toArray(), fn(GradeTeacher $gradeTeacher) => $gradeTeacher->getType() === GradeTeacherType::Primary));
223
224
            $substitutionalGradeTeachers = array_map(fn(GradeTeacher $gradeTeacher) => $gradeTeacher->getTeacher(), array_filter($grade->getTeachers()->toArray(), fn(GradeTeacher $gradeTeacher) => $gradeTeacher->getType() === GradeTeacherType::Substitute));
225
        }
226
227
        $tuitions = [ ];
228
229
        if($studyGroupFilterView->getCurrentStudyGroup() !== null) {
230
            if($studyGroupFilterView->getCurrentStudyGroup()->getType() === StudyGroupType::Grade) {
231
                $tuitions = $tuitionRepository->findAllByGrades($studyGroupFilterView->getCurrentStudyGroup()->getGrades()->toArray(), $sectionFilterView->getCurrentSection());
0 ignored issues
show
Bug introduced by
It seems like $sectionFilterView->getCurrentSection() can also be of type null; however, parameter $section of App\Repository\TuitionRe...face::findAllByGrades() does only seem to accept App\Entity\Section, 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

231
                $tuitions = $tuitionRepository->findAllByGrades($studyGroupFilterView->getCurrentStudyGroup()->getGrades()->toArray(), /** @scrutinizer ignore-type */ $sectionFilterView->getCurrentSection());
Loading history...
232
            } else {
233
                $tuitions = $studyGroupFilterView->getCurrentStudyGroup()->getTuitions()->toArray();
234
            }
235
        }
236
237
        $tuitions = array_filter($tuitions, fn(Tuition $tuition) => $tuition->getSection() === $sectionFilterView->getCurrentSection());
238
239
        $sorter->sort($tuitions, TuitionStrategy::class);
240
241
        return $this->renderWithMessages('lists/study_groups.html.twig', [
242
            'sectionFilter' => $sectionFilterView,
243
            'studyGroupFilter' => $studyGroupFilterView,
244
            'studentFilter' => $studentFilterView,
245
            'study_groups' => $studyGroups,
246
            'memberships' => $memberships,
247
            'students' => $students,
248
            'grade' => $grade,
249
            'gradeTeachers' => $gradeTeachers,
250
            'substitutionalGradeTeachers' => $substitutionalGradeTeachers,
251
            'tuitions' => $tuitions,
252
            'today' => $this->dateHelper->getToday(),
253
            'last_import' => $this->importDateTimeRepository->findOneByEntityClass(StudyGroup::class)
254
        ]);
255
    }
256
257
    #[Route(path: '/study_groups/{uuid}/export', name: 'export_studygroup')]
258
    public function exportStudyGroup(StudyGroup $studyGroup, StudyGroupCsvExporter $csvExporter): Response {
259
        $this->denyAccessUnlessGranted(ListsVoter::StudyGroups);
260
261
        return $csvExporter->getCsvResponse($studyGroup);
262
    }
263
264
    #[Route(path: '/teachers', name: 'list_teachers')]
265
    public function teachers(SubjectFilter $subjectFilter, TeacherTagFilter $tagFilter, TeacherRepositoryInterface $teacherRepository,
266
                             SectionResolverInterface $sectionResolver, Request $request): Response {
267
        $this->denyAccessUnlessGranted(ListsVoter::Teachers);
268
269
        $subjectFilterView = $subjectFilter->handle($request->query->get('subject', null));
270
        $tagFilterView = $tagFilter->handle($request->query->get('tag', null));
271
272
        $teachers = $teacherRepository->findAllBySubjectAndTag($subjectFilterView->getCurrentSubject(), $tagFilterView->getCurrentTag());
273
274
        if($tagFilterView->getCurrentTag() !== null && $tagFilterView->getCurrentTag()->getId() === null) {
0 ignored issues
show
introduced by
The condition $tagFilterView->getCurrentTag()->getId() === null is always false.
Loading history...
275
            $teachers = $this->filterImplicitTeacherTag($teachers, $tagFilterView->getCurrentTag());
276
        }
277
278
        $groups = $this->grouper->group($teachers, TeacherFirstCharacterStrategy::class);
279
        $this->sorter->sort($groups, TeacherFirstCharacterGroupStrategy::class);
280
        $this->sorter->sortGroupItems($groups, TeacherStrategy::class);
281
282
        return $this->renderWithMessages('lists/teachers.html.twig', [
283
            'groups' => $groups,
284
            'subjectFilter' => $subjectFilterView,
285
            'tagFilter' => $tagFilterView,
286
            'section' => $sectionResolver->getCurrentSection(),
287
            'last_import' => $this->importDateTimeRepository->findOneByEntityClass(Teacher::class)
288
        ]);
289
    }
290
291
    /**
292
     * @param Teacher[] $teachers
293
     * @return Teacher[]
294
     */
295
    private function filterImplicitTeacherTag(array $teachers, TeacherTag $tag): array {
296
        return array_filter($teachers, function(Teacher $teacher) use ($tag) {
297
            /** @var GradeTeacher $gradeTeacher */
298
            foreach($teacher->getGrades() as $gradeTeacher) {
299
                if($gradeTeacher->getType() === GradeTeacherType::Primary && $tag->getUuid()->toString() === TeacherTag::GradeTeacherTagUuid) {
300
                    return true;
301
                }
302
303
                if($gradeTeacher->getType() === GradeTeacherType::Substitute && $tag->getUuid()->toString() === TeacherTag::SubstituteGradeTeacherTagUuid) {
304
                    return true;
305
                }
306
            }
307
308
            return false;
309
        });
310
    }
311
312
    #[Route(path: '/privacy', name: 'list_privacy')]
313
    public function privacy(SectionResolverInterface $sectionResolver, StudyGroupFilter $studyGroupFilter, Request $request, StudentRepositoryInterface $studentRepository, PrivacyCategoryRepositoryInterface $privacyCategoryRepository): Response {
314
        $this->denyAccessUnlessGranted(ListsVoter::Privacy);
315
316
        /** @var User $user */
317
        $user = $this->getUser();
318
319
        $q = $request->query->get('q', null);
320
        $studygroupView = $studyGroupFilter->handle($request->query->get('study_group', null), $sectionResolver->getCurrentSection(), $user);
321
322
        // Filter categories
323
        $categories = $privacyCategoryRepository->findAll();
324
        $filteredCategories = [ ];
325
326
        foreach($categories as $category) {
327
            if($request->query->get($category->getUuid()->toString()) === '✓') {
328
                $filteredCategories[] = $category->getUuid()->toString();
329
            }
330
        }
331
332
        $students = [ ];
333
334
        if($q !== null) {
335
            $students = $studentRepository->findAllByQuery($q);
336
        } else if($studygroupView->getCurrentStudyGroup() !== null) {
337
            $students = $studentRepository->findAllByStudyGroups([$studygroupView->getCurrentStudyGroup()]);
338
        }
339
340
        $this->sorter->sort($students, StudentStrategy::class);
341
342
        return $this->render('lists/privacy.html.twig', [
343
            'students' => $students,
344
            'categories' => $categories,
345
            'filteredCategories' => $filteredCategories,
346
            'q' => $q,
347
            'section' => $sectionResolver->getCurrentSection(),
348
            'studyGroupFilter' => $studygroupView,
349
            'isStart' => $request->query->has('q') === false && $request->query->has('study_group') === false,
350
            'last_import_categories' => $this->importDateTimeRepository->findOneByEntityClass(PrivacyCategory::class),
351
            'last_import_students' => $this->importDateTimeRepository->findOneByEntityClass(Student::class)
352
        ]);
353
    }
354
355
    #[Route('/lms', name: 'list_lms')]
356
    public function lms(Request $request, LearningManagementSystemFilter $lmsFilter, SectionResolverInterface $sectionResolver, StudyGroupFilter $studyGroupFilter, StudentRepositoryInterface $studentRepository) {
357
        $this->denyAccessUnlessGranted(ListsVoter::LearningManagementSystems);
358
359
        /** @var User $user */
360
        $user = $this->getUser();
361
362
        $lmsFilterView = $lmsFilter->handle($request->query->get('lms'));
363
        $studyGroupFilterView = $studyGroupFilter->handle($request->query->get('study_group', null), $sectionResolver->getCurrentSection(), $user);
364
365
        $students = [ ];
366
367
        if($studyGroupFilterView->getCurrentStudyGroup() !== null) {
368
            $students = $studentRepository->findAllByStudyGroups([$studyGroupFilterView->getCurrentStudyGroup()]);
369
        }
370
371
        $this->sorter->sort($students, StudentStrategy::class);
372
373
        return $this->render('lists/lms.html.twig', [
374
            'students' => $students,
375
            'lmsFilter' => $lmsFilterView,
376
            'studyGroupFilter' => $studyGroupFilterView,
377
            'last_import_lms' => $this->importDateTimeRepository->findOneByEntityClass(LearningManagementSystem::class),
378
            'last_import_students' => $this->importDateTimeRepository->findOneByEntityClass(Student::class)
379
        ]);
380
    }
381
382
    #[Route('/lms/{lms}/{studyGroup}/export', name: 'export_lms')]
383
    #[ParamConverter('lms', options: ['mapping' => ['lms' => 'uuid']])]
384
    #[ParamConverter('studyGroup', options: ['mapping' => ['studyGroup' => 'uuid']])]
385
    public function exportLms(LearningManagementSystem $lms, StudyGroup $studyGroup, LearningManagementSystemInfoCsvExporter $exporter): Response {
386
        return $exporter->getCsvResponse($lms, $studyGroup);
387
    }
388
}