Passed
Pull Request — master (#1969)
by Janko
22:34 queued 10:03
created

ShipMover   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 342
Duplicated Lines 0 %

Test Coverage

Coverage 33.73%

Importance

Changes 0
Metric Value
eloc 148
dl 0
loc 342
ccs 57
cts 169
cp 0.3373
rs 8.8798
c 0
b 0
f 0
wmc 44

13 Methods

Rating   Name   Duplication   Size   Complexity  
A leaveFleetIfNotFleetLeader() 0 5 4
A moveShipsByOneField() 0 37 4
A alertReactionCheck() 0 24 5
A addInformation() 0 3 1
A saveShips() 0 11 4
A initTractoredShips() 0 16 3
A addInformationMerge() 0 3 1
A __construct() 0 8 1
A initWrappers() 0 8 3
A areAllShipsDestroyed() 0 3 1
A checkAndMove() 0 43 3
B travelFlightRoute() 0 71 8
B postFlightInformations() 0 54 6

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Stu\Module\Spacecraft\Lib\Movement;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use Doctrine\Common\Collections\Collection;
7
use Override;
0 ignored issues
show
Bug introduced by
The type Override 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...
8
use Stu\Lib\Information\InformationWrapper;
9
use Stu\Module\PlayerSetting\Lib\UserEnum;
0 ignored issues
show
Bug introduced by
The type Stu\Module\PlayerSetting\Lib\UserEnum 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...
10
use Stu\Module\Spacecraft\Lib\Battle\AlertDetection\AlertReactionFacadeInterface;
11
use Stu\Module\Ship\Lib\Fleet\LeaveFleetInterface;
12
use Stu\Module\Spacecraft\Lib\Message\MessageCollection;
13
use Stu\Module\Spacecraft\Lib\Message\MessageCollectionInterface;
14
use Stu\Module\Spacecraft\Lib\Message\MessageFactoryInterface;
15
use Stu\Module\Spacecraft\Lib\Movement\Component\PreFlight\ConditionCheckResult;
16
use Stu\Module\Spacecraft\Lib\Movement\Component\PreFlight\PreFlightConditionsCheckInterface;
17
use Stu\Module\Spacecraft\Lib\Movement\Route\FlightRouteInterface;
18
use Stu\Module\Spacecraft\Lib\SpacecraftWrapperInterface;
19
use Stu\Orm\Entity\ShipInterface;
20
use Stu\Orm\Entity\SpacecraftInterface;
21
use Stu\Orm\Repository\SpacecraftRepositoryInterface;
22
23
//TODO unit tests
24
final class ShipMover implements ShipMoverInterface
25
{
26 2
    public function __construct(
27
        private SpacecraftRepositoryInterface $spacecraftRepository,
28
        private ShipMovementInformationAdderInterface $shipMovementInformationAdder,
29
        private PreFlightConditionsCheckInterface $preFlightConditionsCheck,
30
        private LeaveFleetInterface $leaveFleet,
31
        private AlertReactionFacadeInterface $alertReactionFacade,
32
        private MessageFactoryInterface $messageFactory
33 2
    ) {}
34
35 1
    #[Override]
36
    public function checkAndMove(
37
        SpacecraftWrapperInterface $leadWrapper,
38
        FlightRouteInterface $flightRoute
39
    ): MessageCollectionInterface {
40
41 1
        $messages = new MessageCollection();
42
43 1
        $leadSpacecraft = $leadWrapper->get();
44 1
        $leadSpacecraftName = $leadSpacecraft->getName();
45 1
        $isFleetMode = $leadSpacecraft instanceof ShipInterface ? $leadSpacecraft->isFleetLeader() : false;
46
47 1
        $wrappers = $this->initWrappers($leadWrapper, $isFleetMode);
48 1
        $initialTractoredShips = $this->initTractoredShips($wrappers);
49
50
        // fly until destination arrived
51 1
        $hasTravelled = $this->travelFlightRoute(
52 1
            $leadWrapper,
53 1
            $wrappers,
54 1
            $isFleetMode,
55 1
            $flightRoute,
56 1
            $messages
57 1
        );
58
59
        //skip save and log info if flight did not happen
60 1
        if (!$hasTravelled) {
61 1
            return $messages;
62
        }
63
64
        // save all ships
65
        $this->saveShips($wrappers, $initialTractoredShips);
66
67
        // add post flight informations
68
        $this->postFlightInformations(
69
            $leadWrapper,
70
            $leadSpacecraftName,
71
            $wrappers,
72
            $flightRoute,
73
            $isFleetMode,
74
            $messages
75
        );
76
77
        return $messages;
78
    }
79
80
    /** @return Collection<int, covariant SpacecraftWrapperInterface> */
81 1
    private function initWrappers(SpacecraftWrapperInterface $leadWrapper, bool $isFleetMode): Collection
82
    {
83 1
        $fleetWrapper = $leadWrapper->getFleetWrapper();
84
85 1
        return
86 1
            $isFleetMode && $fleetWrapper !== null
87
            ? $fleetWrapper->getShipWrappers()
88 1
            : new ArrayCollection([$leadWrapper->get()->getId() => $leadWrapper]);
89
    }
90
91
    /** @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers */
0 ignored issues
show
Bug introduced by
The type Stu\Module\Spacecraft\Lib\Movement\covariant 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...
92 1
    private function travelFlightRoute(
93
        SpacecraftWrapperInterface $leadWrapper,
94
        Collection $wrappers,
95
        bool $isFleetMode,
96
        FlightRouteInterface $flightRoute,
97
        MessageCollectionInterface $messages
98
    ): bool {
99
100 1
        $hasTravelled = false;
101 1
        $fleetWrapper = $leadWrapper->getFleetWrapper();
102 1
        $hasToLeaveFleet = $fleetWrapper !== null && !$isFleetMode;
103
104 1
        $isFixedFleetMode = $isFleetMode
105 1
            && $fleetWrapper !== null
106 1
            && $fleetWrapper->get()->isFleetFixed();
107
108 1
        while (!$flightRoute->isDestinationArrived()) {
109 1
            $nextWaypoint = $flightRoute->getNextWaypoint();
110
111
            // nächstes Feld nicht passierbar
112 1
            if (!$nextWaypoint->getFieldType()->getPassable()) {
113
                $flightRoute->abortFlight();
114
                $this->addInformation('Das nächste Feld kann nicht passiert werden', $messages);
115
                break;
116
            }
117
118 1
            $activeWrappers = $wrappers->filter(fn(SpacecraftWrapperInterface $wrapper): bool => !$wrapper->get()->isDestroyed());
119
120
            // check all flight pre conditions
121 1
            $conditionCheckResult = $this->preFlightConditionsCheck->checkPreconditions(
122 1
                $leadWrapper,
123 1
                $activeWrappers->toArray(),
124 1
                $flightRoute,
125 1
                $isFixedFleetMode
126 1
            );
127
128 1
            if (!$conditionCheckResult->isFlightPossible()) {
129 1
                $flightRoute->abortFlight();
130 1
                $this->addInformation('Der Weiterflug wurde aus folgenden Gründen abgebrochen:', $messages);
131 1
                $this->addInformationMerge($conditionCheckResult->getInformations(), $messages);
132 1
                break;
133
            }
134
135
            $this->addInformationMerge($conditionCheckResult->getInformations(), $messages);
136
137
            $movedTractoredShipWrappers = [];
138
139
            // move every possible ship by one field
140
            $this->moveShipsByOneField(
141
                $activeWrappers,
142
                $flightRoute,
143
                $conditionCheckResult,
144
                $hasToLeaveFleet,
145
                $hasTravelled,
146
                $messages
147
            );
148
149
            // alert reaction check
150
            $this->alertReactionCheck(
151
                $leadWrapper,
152
                $movedTractoredShipWrappers,
153
                $messages
154
            );
155
156
            if ($this->areAllShipsDestroyed($activeWrappers)) {
157
                $flightRoute->abortFlight();
158
                $this->addInformation('Es wurden alle Schiffe zerstört', $messages);
159
            }
160
        }
161
162 1
        return $hasTravelled;
163
    }
164
165
    /**
166
     * @param Collection<int, SpacecraftWrapperInterface> $activeWrappers
167
     */
168
    private function moveShipsByOneField(
169
        Collection $activeWrappers,
170
        FlightRouteInterface $flightRoute,
171
        ConditionCheckResult $conditionCheckResult,
172
        bool $hasToLeaveFleet,
173
        bool &$hasTravelled,
174
        MessageCollectionInterface $messages
175
    ): void {
176
177
        foreach ($activeWrappers as $wrapper) {
178
179
            $ship = $wrapper->get();
180
181
            if ($conditionCheckResult->isNotBlocked($ship)) {
182
183
                $this->leaveFleetIfNotFleetLeader($ship, $hasToLeaveFleet, $messages);
184
185
                $flightRoute->enterNextWaypoint(
186
                    $wrapper,
187
                    $messages
188
                );
189
190
                $tractoredShipWrapper = $wrapper->getTractoredShipWrapper();
191
                if ($tractoredShipWrapper !== null) {
192
                    $flightRoute->enterNextWaypoint(
193
                        $tractoredShipWrapper,
194
                        $messages
195
                    );
196
197
                    $movedTractoredShipWrappers[] = [$wrapper->get(), $tractoredShipWrapper];
198
                }
199
200
                $hasTravelled = true;
201
            }
202
        }
203
204
        $flightRoute->stepForward();
205
    }
206
207
    /** @param array<array{0: ShipInterface, 1: SpacecraftWrapperInterface}> $movedTractoredShipWrappers */
208
    private function alertReactionCheck(
209
        SpacecraftWrapperInterface $leadWrapper,
210
        array $movedTractoredShipWrappers,
211
        MessageCollectionInterface $messages
212
    ): void {
213
        $alertRedInformations = new InformationWrapper();
214
        $this->alertReactionFacade->doItAll($leadWrapper, $alertRedInformations);
215
216
        if (!$alertRedInformations->isEmpty()) {
217
            $this->addInformationMerge($alertRedInformations->getInformations(), $messages);
218
        }
219
220
        // alert red check for tractored ships
221
        foreach ($movedTractoredShipWrappers as [$tractoringShip, $tractoredShipWrapper]) {
222
            if (!$tractoringShip->isDestroyed()) {
223
                $alertRedInformations = new InformationWrapper();
224
                $this->alertReactionFacade->doItAll(
225
                    $tractoredShipWrapper,
226
                    $alertRedInformations,
227
                    $tractoringShip
228
                );
229
230
                if (!$alertRedInformations->isEmpty()) {
231
                    $this->addInformationMerge($alertRedInformations->getInformations(), $messages);
232
                }
233
            }
234
        }
235
    }
236
237
    /**
238
     * @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers
239
     *
240
     * @return array<ShipInterface>
241
     */
242 1
    private function initTractoredShips(Collection $wrappers): array
243
    {
244 1
        $tractoredShips = [];
245
246 1
        foreach ($wrappers as $fleetShipWrapper) {
247 1
            $fleetShip = $fleetShipWrapper->get();
248
249 1
            $tractoredShip = $fleetShip->getTractoredShip();
250
            if (
251 1
                $tractoredShip !== null
252
            ) {
253
                $tractoredShips[] = $tractoredShip;
254
            }
255
        }
256
257 1
        return $tractoredShips;
258
    }
259
260
    private function leaveFleetIfNotFleetLeader(SpacecraftInterface $ship, bool $hasToLeaveFleet, MessageCollectionInterface $messages): void
261
    {
262
        if ($hasToLeaveFleet && $ship instanceof ShipInterface) {
263
            if ($this->leaveFleet->leaveFleet($ship)) {
264
                $this->addInformation(sprintf('Die %s hat die Flotte verlassen', $ship->getName()), $messages);
265
            }
266
        }
267
    }
268
269
    /**
270
     * @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers
271
     * @param array<ShipInterface> $initialTractoredShips
272
     */
273
    private function saveShips(Collection $wrappers, array $initialTractoredShips): void
274
    {
275
        foreach ($wrappers as $wrapper) {
276
            $ship = $wrapper->get();
277
            if (!$ship->isDestroyed()) {
278
                $this->spacecraftRepository->save($ship);
279
            }
280
        }
281
282
        foreach ($initialTractoredShips as $tractoredShip) {
283
            $this->spacecraftRepository->save($tractoredShip);
284
        }
285
    }
286
287
    /**
288
     * @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers
289
     */
290
    private function postFlightInformations(
291
        SpacecraftWrapperInterface $leadWrapper,
292
        string $leadSpacecraftName,
293
        Collection $wrappers,
294
        FlightRouteInterface $flightRoute,
295
        bool $isFleetMode,
296
        MessageCollectionInterface $messages
297
    ): void {
298
299
        //add tractor info
300
        foreach ($wrappers as $wrapper) {
301
            $ship = $wrapper->get();
302
303
            $tractoredShip = $ship->getTractoredShip();
304
            if ($tractoredShip !== null) {
305
                $this->shipMovementInformationAdder->pulledTractoredShip(
306
                    $ship,
307
                    $tractoredShip,
308
                    $flightRoute->getRouteMode(),
309
                    $messages
310
                );
311
            }
312
        }
313
314
        $leadSpacecraft = $leadWrapper->get();
315
316
        //add destination info
317
        if ($this->areAllShipsDestroyed($wrappers)) {
318
            $this->shipMovementInformationAdder->reachedDestinationDestroyed(
319
                $leadSpacecraft,
320
                $leadSpacecraftName,
321
                $isFleetMode,
322
                $flightRoute->getRouteMode(),
323
                $messages
324
            );
325
        } else {
326
            $this->shipMovementInformationAdder->reachedDestination(
327
                $leadSpacecraft,
328
                $isFleetMode,
329
                $flightRoute->getRouteMode(),
330
                $messages
331
            );
332
        }
333
334
        //add info about anomalies
335
        foreach ($leadWrapper->get()->getLocation()->getAnomalies() as $anomaly) {
336
            $this->addInformation(sprintf(
337
                '[b][color=yellow]In diesem Sektor befindet sich eine %s[/color][/b]',
338
                $anomaly->getAnomalyType()->getName()
339
            ), $messages);
340
        }
341
        // add info about buyos
342
        foreach ($leadWrapper->get()->getLocation()->getBuoys() as $buoy) {
343
            $this->addInformation(sprintf('[b][color=yellow]Boje entdeckt: [/color][/b]%s', $buoy->getText()), $messages);
344
        }
345
    }
346
347
    /**
348
     * @param Collection<int, covariant SpacecraftWrapperInterface> $wrappers
349
     */
350
    private function areAllShipsDestroyed(Collection $wrappers): bool
351
    {
352
        return !$wrappers->exists(fn(int $key, SpacecraftWrapperInterface $wrapper): bool => !$wrapper->get()->isDestroyed());
353
    }
354
355 1
    private function addInformation(string $value, MessageCollectionInterface $messages): void
356
    {
357 1
        $this->addInformationMerge([$value], $messages);
358
    }
359
360
    /**
361
     * @param array<string> $value
362
     */
363 1
    private function addInformationMerge(array $value, MessageCollectionInterface $messages): void
364
    {
365 1
        $messages->add($this->messageFactory->createMessage(UserEnum::USER_NOONE, null, $value));
366
    }
367
}
368