SubstitutionImporter   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 48
eloc 109
c 0
b 0
f 0
dl 0
loc 194
rs 8.5599

7 Methods

Rating   Name   Duplication   Size   Complexity  
D import() 0 92 18
A getSubjectOverridesMap() 0 8 2
B getType() 0 43 7
A __construct() 0 2 1
A matchesFlag() 0 2 1
A sort() 0 17 5
C isSubstitutionOfNextLesson() 0 13 14

How to fix   Complexity   

Complex Class

Complex classes like SubstitutionImporter 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 SubstitutionImporter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace App\Untis\Gpu\Substitution;
4
5
use App\Import\Importer;
6
use App\Import\ImportResult;
7
use App\Import\SubstitutionsImportStrategy;
8
use App\Request\Data\SubstitutionData;
9
use App\Request\Data\SubstitutionsData;
10
use App\Settings\UntisSettings;
11
use DateTime;
12
use League\Csv\Reader;
0 ignored issues
show
Bug introduced by
The type League\Csv\Reader was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
14
class SubstitutionImporter {
15
    public function __construct(private Importer $importer, private SubstitutionsImportStrategy $strategy, private SubstitutionReader $gpuReader, private UntisSettings $settings)
16
    {
17
    }
18
19
    private function sort(Substitution $substitutionA, Substitution $substitutionB): int {
20
        if($substitutionA->getDate() == $substitutionB->getDate()) {
21
            if(strnatcmp($substitutionA->getTeacher(), $substitutionB->getTeacher()) === 0) {
0 ignored issues
show
Bug introduced by
It seems like $substitutionA->getTeacher() can also be of type null; however, parameter $string1 of strnatcmp() does only seem to accept string, 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

21
            if(strnatcmp(/** @scrutinizer ignore-type */ $substitutionA->getTeacher(), $substitutionB->getTeacher()) === 0) {
Loading history...
Bug introduced by
It seems like $substitutionB->getTeacher() can also be of type null; however, parameter $string2 of strnatcmp() does only seem to accept string, 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

21
            if(strnatcmp($substitutionA->getTeacher(), /** @scrutinizer ignore-type */ $substitutionB->getTeacher()) === 0) {
Loading history...
22
                if(strnatcmp($substitutionA->getSubject(),  $substitutionB->getSubject()) === 0) {
23
                    // Sort by lesson last
24
                    return $substitutionA->getLesson() - $substitutionB->getLesson();
25
                }
26
27
                return strnatcmp($substitutionA->getSubject(), $substitutionB->getSubject());
28
            }
29
30
            // Sort by teacher second
31
            return strnatcmp($substitutionA->getTeacher(), $substitutionB->getTeacher());
32
        }
33
34
        // Sort by date first
35
        return $substitutionA->getDate() < $substitutionB ? 1 : -1;
36
    }
37
38
    public function import(Reader $reader, DateTime $start, DateTime $end, bool $suppressNotifications): ImportResult {
39
        $start->setTime(0,0,0);
40
        $end->setTime(0,0,0);
41
42
        $data = new SubstitutionsData();
43
        $data->setSuppressNotifications($suppressNotifications);
44
        $substitutions = [ ];
45
46
        $subjectOverrideMap = $this->getSubjectOverridesMap();
47
        $gpuSubstitutions = $this->gpuReader->readGpu($reader);
48
49
        $gpuSubstitutions = array_filter($gpuSubstitutions, function(Substitution $substitution) use ($start, $end) {
50
            if($substitution->getDate() < $start || $substitution->getDate() > $end) {
51
                return false;
52
            }
53
54
            if($substitution->getId() === 0) {
55
                return false;
56
            }
57
58
            return true;
59
        });
60
61
        usort($gpuSubstitutions, [ $this, 'sort' ]);
62
63
        for($idx = 0; $idx < count($gpuSubstitutions); $idx++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
64
            $substitution = $gpuSubstitutions[$idx];
65
66
            $includeNextLesson = false;
67
68
            if($this->settings->isSubstitutionCollapsingEnabled() && $idx+1 < count($gpuSubstitutions)) {
69
                $includeNextLesson = $this->isSubstitutionOfNextLesson($substitution, $gpuSubstitutions[$idx + 1]);
70
            }
71
72
            $substitutionData = (new SubstitutionData())
73
                ->setId((string)$substitution->getId())
74
                ->setDate($substitution->getDate())
75
                ->setLessonStart($substitution->getLesson())
76
                ->setLessonEnd($substitution->getLesson() + ($includeNextLesson ? 1 : 0))
77
                ->setRooms($substitution->getRooms())
78
                ->setReplacementRooms($substitution->getReplacementRooms())
79
                ->setGrades($substitution->getGrades())
80
                ->setReplacementGrades($substitution->getReplacementGrades())
81
                ->setText($substitution->getRemark())
82
                ->setType($this->getType($substitution));
83
84
            if($substitution->getTeacher() !== null) {
85
                $substitutionData->setTeachers([$substitution->getTeacher()]);
86
            } else {
87
                $substitutionData->setTeachers([]);
88
            }
89
90
            if($substitution->getReplacementTeacher() !== null) {
91
                $substitutionData->setReplacementTeachers([$substitution->getReplacementTeacher()]);
92
            } else {
93
                $substitutionData->setReplacementTeachers([]);
94
            }
95
96
            if(!empty($substitution->getSubject()) && array_key_exists($substitution->getSubject(), $subjectOverrideMap)) {
97
                $substitutionData->setSubject($subjectOverrideMap[$substitution->getSubject()]);
98
            } else {
99
                $substitutionData->setSubject($substitution->getSubject());
100
            }
101
102
            if(!empty($substitution->getReplacementSubject()) && array_key_exists($substitution->getReplacementSubject(), $subjectOverrideMap)) {
103
                $substitutionData->setReplacementSubject($subjectOverrideMap[$substitution->getReplacementSubject()]);
104
            } else {
105
                $substitutionData->setReplacementSubject($substitution->getReplacementSubject());
106
            }
107
108
            if(!empty($substitution->getSubject()) && empty($substitution->getReplacementSubject())) {
109
                $substitutionData->setReplacementSubject($substitutionData->getSubject());
110
            }
111
112
            $substitutionData->setStartsBefore($substitutionData->getType() === 'Pausenaufsichtsvertretung');
113
114
            if($this->matchesFlag($substitution->getFlags(), SubstitutionFlag::DoNotPrintFlag)) {
115
                continue;
116
            }
117
118
            $substitutions[] = $substitutionData;
119
120
            if($includeNextLesson) {
121
                // Skip next substitution as it is already handled.
122
                $idx++;
123
            }
124
        }
125
126
        $data->setSubstitutions($substitutions);
127
128
        $result = $this->importer->import($data, $this->strategy);
129
        return $result;
130
    }
131
132
    private function getSubjectOverridesMap(): array {
133
        $map = [ ];
134
135
        foreach($this->settings->getSubjectOverrides() as $override) {
136
            $map[$override['untis']] = $override['override'];
137
        }
138
139
        return $map;
140
    }
141
142
    private function getType(Substitution $substitution): string {
143
        $map = [
144
            'S' => 'Betreuung',
145
            'A' => 'Sondereinsatz',
146
            'L' => 'Freisetzung',
147
            'R' => 'Raumvertretung',
148
            'B' => 'Pausenaufsichtsvertretung',
149
            'E' => 'Klausur'
150
        ];
151
152
        if($substitution->getType() !== null && array_key_exists($substitution->getType()->value, $map)) {
153
            return $map[$substitution->getType()->value];
154
        }
155
156
        $map = [
157
            SubstitutionFlag::Cancellation => 'Entfall',
158
            SubstitutionFlag::Supervision => 'Betreuung',
159
            SubstitutionFlag::SpecialDuty => 'Sondereinsatz',
160
            SubstitutionFlag::ShiftedFrom => 'Vertretung',
161
            SubstitutionFlag::Release => 'Freisetzung',
162
            SubstitutionFlag::PlusAsStandIn => 'Plus als Vertreter',
163
            SubstitutionFlag::PartialStandIn => 'Teilvertretung',
164
            SubstitutionFlag::ShiftedTo => 'Vertretung',
165
            SubstitutionFlag::RoomExchange => 'Raumvertretung',
166
            SubstitutionFlag::SupervisionExchange => 'Pausenaufsichtsvertretung',
167
            SubstitutionFlag::NoLesson => 'Unterrichtsfrei'
168
        ];
169
170
        /**
171
         * @var int $flag
172
         * @var string $value
173
         */
174
        foreach($map as $flag => $value) {
175
            if($this->matchesFlag($substitution->getFlags(), $flag)) {
176
                if($flag === SubstitutionFlag::PlusAsStandIn && empty($substitution->getReplacementTeacher())) {
177
                    return 'Eigenverantwortliches Arbeiten';
178
                }
179
180
                return $value;
181
            }
182
        }
183
184
        return 'Vertretung';
185
    }
186
187
    private function matchesFlag(int $value, int $flag): bool {
188
        return ($value & $flag) == $flag;
189
    }
190
191
    /**
192
     * Checks whether the substitution are identical but their IDs and their lessons. These values of the second
193
     * substitution need to be increments of the ones of the first one.
194
     */
195
    private function isSubstitutionOfNextLesson(Substitution $first, Substitution $second): bool {
196
        return $first->getLesson() + 1 === $second->getLesson()
197
            && $first->getRooms() === $second->getRooms()
198
            && $first->getReplacementRooms() === $second->getReplacementRooms()
199
            && $first->getGrades() === $second->getGrades()
200
            && $first->getReplacementGrades() === $second->getReplacementGrades()
201
            && $first->getSubject() === $second->getSubject()
202
            && $first->getReplacementSubject() === $second->getReplacementSubject()
203
            && $first->getTeacher() === $second->getTeacher()
204
            && $first->getReplacementTeacher() === $second->getReplacementTeacher()
205
            && $first->getFlags() === $second->getFlags()
206
            && (($first->getType() === null && $second->getType() === null) || $first->getType() === $second->getType())
207
            && $first->getRemark() === $second->getRemark();
208
    }
209
}