GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( c0206e...9204a9 )
by Alex
05:38 queued 04:00
created

StepChainBuilder   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 335
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 96.03%

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 9
dl 0
loc 335
ccs 121
cts 126
cp 0.9603
rs 8.72
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B build() 0 74 9
A groupRegistrationsByStage() 0 17 3
A getStepsFromStage() 0 15 6
A getConnectorsFromStage() 0 4 1
A isTerminator() 0 4 1
A sort() 0 9 2
A isImplementing() 0 5 1
A assertAdditionsAreUnique() 0 14 3
A assertReplacementsAreValid() 0 10 3
A assertRemovalsAreValid() 0 10 3
A assertRemovalsDoNotAffectDependencies() 0 18 5
A assertContextStageExists() 0 8 2
A assertThereIsAtMostOneConnectorPerStage() 0 14 3
A assertThereIsAtLeastOneConnectorPerIntermediaryStage() 0 10 3

How to fix   Complexity   

Complex Class

Complex classes like StepChainBuilder 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 StepChainBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace PSB\Core\Pipeline;
3
4
5
use PSB\Core\Exception\PipelineBuildingException;
6
7
class StepChainBuilder
8
{
9
    /**
10
     * @var string
11
     */
12
    private $rootContextClass;
13
14
    /**
15
     * @var StepRegistration[]
16
     */
17
    private $additions = [];
18
19
    /**
20
     * @var StepReplacement[]
21
     */
22
    private $replacements = [];
23
24
    /**
25
     * @var StepRemoval[]
26
     */
27
    private $removals = [];
28
29
    /**
30
     * @param string             $rootContextClass
31
     * @param StepRegistration[] $additions
32
     * @param StepReplacement[]  $replacements
33
     * @param StepRemoval[]      $removals
34
     */
35 13
    public function __construct($rootContextClass, array $additions, array $replacements, array $removals)
36
    {
37 13
        $this->rootContextClass = $rootContextClass;
38 13
        $this->additions = $additions;
39 13
        $this->replacements = $replacements;
40 13
        $this->removals = $removals;
41 13
    }
42
43
    /**
44
     * @return StepRegistration[]
45
     *
46
     * @throws PipelineBuildingException
47
     */
48 11
    public function build()
49
    {
50 11
        $this->assertAdditionsAreUnique();
51
52
        /** @var StepRegistration[] $registrations */
53 10
        $registrations = [];
54
55 10
        foreach ($this->additions as $addition) {
56 10
            $registrations[$addition->getStepId()] = $addition;
57
        }
58
59 10
        $this->assertReplacementsAreValid($registrations);
60
61 9
        foreach ($this->replacements as $replacement) {
62
            $registrations[$replacement->getIdToReplace()]->replaceWith($replacement);
63
        }
64
65 9
        $this->assertRemovalsAreValid($registrations);
66 8
        $this->assertRemovalsDoNotAffectDependencies($registrations);
67
68 7
        foreach ($this->removals as $removal) {
69
            if (isset($registrations[$removal->getIdToRemove()])) {
70
                unset($registrations[$removal->getIdToRemove()]);
71
            }
72
        }
73
74 7
        if (empty($registrations)) {
75
            return [];
76
        }
77
78 7
        $stages = $this->groupRegistrationsByStage($registrations);
79
80 7
        $this->assertContextStageExists($this->rootContextClass, $stages);
81 6
        $currentStage = $stages[$this->rootContextClass];
82 6
        $stageName = $this->rootContextClass;
83
84 6
        $finalOrder = [];
85 6
        $stageNumber = 1;
86 6
        while ($currentStage) {
87 6
            $stageSteps = $this->getStepsFromStage($currentStage);
88 6
            $finalOrder = array_merge($finalOrder, $this->sort($stageSteps));
89
90 6
            $stageConnectors = $this->getConnectorsFromStage($currentStage);
91
92 6
            $this->assertThereIsAtMostOneConnectorPerStage($stageName, $stageConnectors);
93 5
            $this->assertThereIsAtLeastOneConnectorPerIntermediaryStage(
94 5
                $stageNumber,
95 5
                count($stages),
96 5
                $stageName,
97 5
                $stageConnectors
98
            );
99
100 4
            $currentStage = null;
101
102
            /** @var StepRegistration $stageConnector */
103 4
            $stageConnector = reset($stageConnectors);
104 4
            if ($stageConnector) {
105 4
                $finalOrder[] = $stageConnector;
106
107 4
                if (!$this->isTerminator($stageConnector)) {
108
                    /** @var StageConnectorInterface $connectorClass */
109 4
                    $connectorClass = $stageConnector->getStepFqcn();
110 4
                    $stageName = $connectorClass::getNextStageContextClass();
111 4
                    $this->assertContextStageExists($stageName, $stages);
112
113 3
                    $currentStage = $stages[$stageName];
114
                }
115
            }
116
117 3
            $stageNumber++;
118
        }
119
120 3
        return $finalOrder;
121
    }
122
123
    /**
124
     * @param StepRegistration[] $registrations
125
     *
126
     * @return array
127
     */
128 7
    private function groupRegistrationsByStage(array $registrations)
129
    {
130 7
        $stages = [];
131 7
        foreach ($registrations as $registration) {
132
            /** @var PipelineStepInterface $stepClass */
133 7
            $stepClass = $registration->getStepFqcn();
134 7
            $stageName = $stepClass::getStageContextClass();
135
136 7
            if (!isset($stages[$stageName])) {
137 7
                $stages[$stageName] = [];
138
            }
139
140 7
            $stages[$stageName][] = $registration;
141
        }
142
143 7
        return $stages;
144
    }
145
146
    /**
147
     * @param StepRegistration[] $currentStage
148
     * @param bool               $isConnector
149
     *
150
     * @return StepRegistration[]
151
     */
152 6
    private function getStepsFromStage(array $currentStage, $isConnector = false)
153
    {
154 6
        $steps = [];
155 6
        foreach ($currentStage as $step) {
156 6
            if (!$isConnector && !$this->isImplementing($step->getStepFqcn(), StageConnectorInterface::class)) {
157 6
                $steps[$step->getStepId()] = $step;
158
            }
159
160 6
            if ($isConnector && $this->isImplementing($step->getStepFqcn(), StageConnectorInterface::class)) {
161 5
                $steps[$step->getStepId()] = $step;
162
            }
163
        }
164
165 6
        return $steps;
166
    }
167
168
    /**
169
     * @param StepRegistration[] $currentStage
170
     *
171
     * @return StepRegistration[]
172
     */
173 6
    private function getConnectorsFromStage(array $currentStage)
174
    {
175 6
        return $this->getStepsFromStage($currentStage, true);
176
    }
177
178
    /**
179
     * @param StepRegistration $stageConnector
180
     *
181
     * @return bool
182
     */
183 4
    private function isTerminator($stageConnector)
184
    {
185 4
        return $this->isImplementing($stageConnector->getStepFqcn(), PipelineTerminatorInterface::class);
186
    }
187
188
    /**
189
     * @param StepRegistration[] $stageSteps
190
     *
191
     * @return StepRegistration[]
192
     */
193 6
    private function sort(array $stageSteps)
194
    {
195 6
        if (empty($stageSteps)) {
196
            return [];
197
        }
198
199 6
        $dependencyGraphBuilder = new StepDependencyGraphBuilder($stageSteps);
200 6
        return $dependencyGraphBuilder->build()->sort();
201
    }
202
203
    /**
204
     * @param string $fqcn
205
     * @param string $fqin
206
     *
207
     * @return bool
208
     */
209 6
    private function isImplementing($fqcn, $fqin)
210
    {
211 6
        $interfaces = class_implements($fqcn, true);
212 6
        return isset($interfaces[$fqin]);
213
    }
214
215
    /**
216
     * @throws PipelineBuildingException
217
     */
218 11
    private function assertAdditionsAreUnique()
219
    {
220
        /** @var StepRegistration[] $registrations */
221 11
        $registrations = [];
222 11
        foreach ($this->additions as $addition) {
223 11
            if (isset($registrations[$addition->getStepId()])) {
224 1
                $existingStepClass = $registrations[$addition->getStepId()]->getStepFqcn();
225 1
                throw new PipelineBuildingException(
226 1
                    "Step registration with id '{$addition->getStepId()}' is already registered for step '$existingStepClass'."
227
                );
228
            }
229 11
            $registrations[$addition->getStepId()] = $addition;
230
        }
231 10
    }
232
233
    /**
234
     * @param StepRegistration[] $registrations
235
     *
236
     * @throws PipelineBuildingException
237
     */
238 10
    private function assertReplacementsAreValid(array $registrations)
239
    {
240 10
        foreach ($this->replacements as $replacement) {
241 1
            if (!isset($registrations[$replacement->getIdToReplace()])) {
242 1
                throw new PipelineBuildingException(
243 1
                    "You can only replace an existing step registration, '{$replacement->getIdToReplace()}' registration does not exist."
244
                );
245
            }
246
        }
247 9
    }
248
249
    /**
250
     * @param StepRegistration[] $registrations
251
     *
252
     * @throws PipelineBuildingException
253
     */
254 9
    private function assertRemovalsAreValid(array $registrations)
255
    {
256 9
        foreach ($this->removals as $removal) {
257 2
            if (!isset($registrations[$removal->getIdToRemove()])) {
258 1
                throw new PipelineBuildingException(
259 1
                    "You cannot remove step registration with id '{$removal->getIdToRemove()}', registration does not exist."
260
                );
261
            }
262
        }
263 8
    }
264
265
    /**
266
     * @param StepRegistration[] $registrations
267
     *
268
     * @throws PipelineBuildingException
269
     */
270 8
    private function assertRemovalsDoNotAffectDependencies(array $registrations)
271
    {
272 8
        $removalIds = [];
273 8
        foreach ($this->removals as $removal) {
274 1
            $removalIds[$removal->getIdToRemove()] = 0;
275
        }
276
277 8
        foreach ($registrations as $registration) {
278
            /** @var StepRegistrationDependency $dependency */
279 8
            foreach (array_merge($registration->getBefores(), $registration->getAfters()) as $dependency) {
280 2
                if (isset($removalIds[$dependency->getDependsOnId()])) {
281 1
                    throw new PipelineBuildingException(
282 1
                        "You cannot remove step registration with id '{$dependency->getDependsOnId()}', registration with id '{$registration->getStepId()}' depends on it."
283
                    );
284
                }
285
            }
286
        }
287 7
    }
288
289
    /**
290
     * @param string $contextClass
291
     * @param array  $stages
292
     *
293
     * @throws PipelineBuildingException
294
     */
295 7
    private function assertContextStageExists($contextClass, $stages)
296
    {
297 7
        if (!isset($stages[$contextClass])) {
298 2
            throw new PipelineBuildingException(
299 2
                "Can't find any steps/connectors for stage '$contextClass'."
300
            );
301
        }
302 6
    }
303
304
    /**
305
     * @param string             $stageName
306
     * @param StepRegistration[] $stageConnectors
307
     *
308
     */
309 6
    private function assertThereIsAtMostOneConnectorPerStage($stageName, $stageConnectors)
310
    {
311 6
        if (count($stageConnectors) > 1) {
312 1
            $connectorClasses = [];
313 1
            foreach ($stageConnectors as $connector) {
314 1
                $connectorClasses[] = $connector->getStepFqcn();
315
            }
316 1
            $connectors = implode(',', $connectorClasses);
317
318 1
            throw new PipelineBuildingException(
319 1
                "Multiple stage connectors found for stage '$stageName'. Please remove one of: $connectors."
320
            );
321
        }
322 5
    }
323
324
    /**
325
     * @param int                $stageNumber
326
     * @param int                $stageCount
327
     * @param string             $stageName
328
     * @param StepRegistration[] $stageConnectors
329
     *
330
     */
331 5
    private function assertThereIsAtLeastOneConnectorPerIntermediaryStage(
332
        $stageNumber,
333
        $stageCount,
334
        $stageName,
335
        $stageConnectors
336
    ) {
337 5
        if ($stageNumber < $stageCount && count($stageConnectors) == 0) {
338 1
            throw new PipelineBuildingException("No stage connector found for stage '$stageName'.");
339
        }
340 4
    }
341
}
342
343