Passed
Push — master ( d221d7...012d01 )
by Allan
02:58 queued 11s
created

PatchesApplier::apply()   B

Complexity

Conditions 11
Paths 61

Size

Total Lines 78
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 12
Bugs 5 Features 1
Metric Value
cc 11
eloc 44
c 12
b 5
f 1
nc 61
nop 2
dl 0
loc 78
rs 7.3166

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
 * Copyright © Vaimo Group. All rights reserved.
4
 * See LICENSE_VAIMO.txt for license details.
5
 */
6
namespace Vaimo\ComposerPatches\Repository;
7
8
use Composer\Repository\WritableRepositoryInterface as Repository;
9
use Composer\Package\PackageInterface as Package;
10
use Vaimo\ComposerPatches\Patch\Definition as Patch;
11
12
/**
13
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
14
 */
15
class PatchesApplier
16
{
17
    /**
18
     * @var \Vaimo\ComposerPatches\Package\Collector
19
     */
20
    private $packageCollector;
21
22
    /**
23
     * @var \Vaimo\ComposerPatches\Managers\RepositoryManager
24
     */
25
    private $repositoryManager;
26
27
    /**
28
     * @var \Vaimo\ComposerPatches\Package\PatchApplier
29
     */
30
    private $packagePatchApplier;
31
32
    /**
33
     * @var \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator
34
     */
35
    private $queueGenerator;
36
37
    /**
38
     * @var \Vaimo\ComposerPatches\Managers\PatcherStateManager
39
     */
40
    private $patcherStateManager;
41
42
    /**
43
     * @var \Vaimo\ComposerPatches\Repository\StateGenerator
44
     */
45
    private $repositoryStateGenerator;
46
47
    /**
48
     * @var \Vaimo\ComposerPatches\Package\PatchApplier\InfoLogger
49
     */
50
    private $patchInfoLogger;
51
52
    /**
53
     * @var \Vaimo\ComposerPatches\Strategies\OutputStrategy
54
     */
55
    private $outputStrategy;
56
    
57
    /**
58
     * @var \Vaimo\ComposerPatches\Logger
59
     */
60
    private $logger;
61
62
    /**
63
     * @var \Vaimo\ComposerPatches\Patch\DefinitionList\Analyser
64
     */
65
    private $patchListAnalyser;
66
    
67
    /**
68
     * @var \Vaimo\ComposerPatches\Patch\DefinitionList\Transformer
69
     */
70
    private $patchListTransformer;
71
    
72
    /**
73
     * @var \Vaimo\ComposerPatches\Package\PatchApplier\StatusConfig
74
     */
75
    private $statusConfig;
76
    
77
    /**
78
     * @var \Vaimo\ComposerPatches\Utils\PackageUtils
79
     */
80
    private $packageUtils;
81
82
    /**
83
     * @var \Vaimo\ComposerPatches\Utils\DataUtils
84
     */
85
    private $dataUtils;
86
87
    /**
88
     * @var \Vaimo\ComposerPatches\Console\OutputGenerator
89
     */
90
    private $outputGenerator;
91
    
92
    /**
93
     * @param \Vaimo\ComposerPatches\Package\Collector $packageCollector
94
     * @param \Vaimo\ComposerPatches\Managers\RepositoryManager $repositoryManager
95
     * @param \Vaimo\ComposerPatches\Package\PatchApplier $patchApplier
96
     * @param \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator $queueGenerator
97
     * @param \Vaimo\ComposerPatches\Managers\PatcherStateManager $patcherStateManager
98
     * @param \Vaimo\ComposerPatches\Package\PatchApplier\InfoLogger $patchInfoLogger
99
     * @param \Vaimo\ComposerPatches\Strategies\OutputStrategy $outputStrategy
100
     * @param \Vaimo\ComposerPatches\Logger $logger
101
     */
102
    public function __construct(
103
        \Vaimo\ComposerPatches\Package\Collector $packageCollector,
104
        \Vaimo\ComposerPatches\Managers\RepositoryManager $repositoryManager,
105
        \Vaimo\ComposerPatches\Package\PatchApplier $patchApplier,
106
        \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator $queueGenerator,
107
        \Vaimo\ComposerPatches\Managers\PatcherStateManager $patcherStateManager,
108
        \Vaimo\ComposerPatches\Package\PatchApplier\InfoLogger $patchInfoLogger,
109
        \Vaimo\ComposerPatches\Strategies\OutputStrategy $outputStrategy,
110
        \Vaimo\ComposerPatches\Logger $logger
111
    ) {
112
        $this->packageCollector = $packageCollector;
113
        $this->repositoryManager = $repositoryManager;
114
        $this->packagePatchApplier = $patchApplier;
115
        $this->queueGenerator = $queueGenerator;
116
        $this->patcherStateManager = $patcherStateManager;
117
        $this->patchInfoLogger = $patchInfoLogger;
118
        $this->outputStrategy = $outputStrategy;
119
        $this->logger = $logger;
120
121
        $this->repositoryStateGenerator = new \Vaimo\ComposerPatches\Repository\StateGenerator(
122
            $this->packageCollector
123
        );
124
125
        $this->outputGenerator = new \Vaimo\ComposerPatches\Console\OutputGenerator($logger);
126
        
127
        $this->patchListAnalyser = new \Vaimo\ComposerPatches\Patch\DefinitionList\Analyser();
128
        $this->patchListTransformer = new \Vaimo\ComposerPatches\Patch\DefinitionList\Transformer();
129
        $this->statusConfig = new \Vaimo\ComposerPatches\Package\PatchApplier\StatusConfig();
130
        $this->packageUtils = new \Vaimo\ComposerPatches\Utils\PackageUtils();
131
        $this->dataUtils = new \Vaimo\ComposerPatches\Utils\DataUtils();
132
    }
133
134
    /**
135
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
136
     * @SuppressWarnings(PHPMD.NPathComplexity)
137
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
138
     *
139
     * @param Repository $repository
140
     * @param array $patches
141
     * @return bool
142
     * @throws \Vaimo\ComposerPatches\Exceptions\PackageNotFound
143
     * @throws \Vaimo\ComposerPatches\Exceptions\PackageResetException
144
     */
145
    public function apply(Repository $repository, array $patches)
146
    {
147
        $packages = $this->packageCollector->collect($repository);
148
149
        $packagesUpdated = false;
150
151
        $repositoryState = $this->repositoryStateGenerator->generate($repository);
152
        
153
        $applyQueue = $this->queueGenerator->generateApplyQueue($patches, $repositoryState);
154
        $removeQueue = $this->queueGenerator->generateRemovalQueue($applyQueue, $repositoryState);
155
        $resetQueue = $this->queueGenerator->generateResetQueue($applyQueue);
156
        
157
        $applyQueue = array_map('array_filter', $applyQueue);
158
        
159
        $patchQueueFootprints = $this->patchListTransformer->createSimplifiedList($applyQueue);
160
161
        $labels = array_diff_key($this->statusConfig->getLabels(), array('unknown' => true));
162
163
        $applyQueue = $this->updateStatusLabels($applyQueue, $labels);
164
        $removeQueue = $this->updateStatusLabels($removeQueue, $labels);
165
        
166
        foreach ($packages as $packageName => $package) {
167
            $hasPatches = !empty($applyQueue[$packageName]);
168
169
            $patchTargets = $hasPatches ?
170
                $this->patchListAnalyser->getAllTargets(array($applyQueue[$packageName]))
171
                : array($packageName);
172
173
            $itemsToReset = $this->dataUtils->extractItems($resetQueue, $patchTargets);
174
            
175
            $resetResult = array();
176
177
            foreach ($itemsToReset as $targetName) {
178
                $resetTarget = $packages[$targetName];
179
180
                $resetPatches = $this->packageUtils->resetAppliedPatches($resetTarget);
181
                $resetResult[$targetName] = is_array($resetPatches) ? $resetPatches : array();
182
183
                if (!$hasPatches && $resetPatches && !isset($patchQueueFootprints[$targetName])) {
184
                    $this->logger->writeRaw(
185
                        'Resetting patches for <info>%s</info> (%s)',
186
                        array($targetName, count($resetResult[$targetName]))
187
                    );
188
                }
189
190
                $this->repositoryManager->resetPackage($repository, $resetTarget);
191
            }
192
            
193
            $packagesUpdated = $packagesUpdated || (bool)array_filter($resetResult);
194
195
            if (!$hasPatches) {
196
                continue;
197
            }
198
            
199
            $changedTargets = $this->resolveChangedTargets($packages, $patchTargets, $patchQueueFootprints);
200
            
201
            if (empty($changedTargets)) {
202
                continue;
203
            }
204
205
            $queuedPatches = array_filter(
206
                $applyQueue[$packageName],
207
                function ($data) use ($changedTargets) {
208
                    return array_intersect($data[Patch::TARGETS], $changedTargets);
209
                }
210
            );
211
    
212
            $this->updatePackage(
213
                $package,
214
                $repository,
215
                $queuedPatches,
216
                $this->dataUtils->extractValue($removeQueue, $packageName, array())
217
            );
218
            
219
            $packagesUpdated = true;
220
        }
221
222
        return $packagesUpdated;
223
    }
224
    
225
    private function updatePackage(Package $package, Repository $repository, array $additions, array $removals)
226
    {
227
        $muteDepth = null;
228
229
        $packageName = $package->getName();
230
        
231
        if (!$this->shouldAllowOutput($additions, $removals)) {
232
            $muteDepth = $this->logger->mute();
233
        }
234
235
        $this->logger->writeRaw(
236
            'Applying patches for <info>%s</info> (%s)',
237
            array($packageName, count($additions))
238
        );
239
240
        $this->processQueues($package, $repository, $additions, $removals);
241
        
242
        $this->logger->writeNewLine();
243
244
        if ($muteDepth !== null) {
245
            $this->logger->unMute($muteDepth);
246
        }
247
    }
248
249
    private function updateStatusLabels(array $queue, array $labels)
250
    {
251
        foreach ($queue as $target => $group) {
252
            foreach ($group as $path => $item) {
253
                $status = isset($item[Patch::STATUS]) ? $item[Patch::STATUS] : 'unknown';
254
255
                if (!isset($labels[$status])) {
256
                    continue;
257
                }
258
259
                $queue[$target][$path][Patch::STATUS_LABEL] = $labels[$status];
260
            }
261
        }
262
263
        return $queue;
264
    }
265
    
266
    private function processQueues(Package $package, Repository $repository, $additions, $removals)
267
    {
268
        try {
269
            if ($removals) {
270
                $processIndentation = $this->logger->push('~');
271
272
                foreach ($removals as $item) {
273
                    $this->patchInfoLogger->outputPatchInfo($item);
274
                }
275
276
                $this->logger->reset($processIndentation);
277
            }
278
279
            $this->processPatchesForPackage($repository, $package, $additions);
280
        } catch (\Exception $exception) {
281
            $this->logger->unMute();
282
283
            throw $exception;
284
        }
285
    }
286
    
287
    private function resolveChangedTargets(array $packages, array $patchTargets, array $patchFootprints)
288
    {
289
        $changesMap = array();
290
291
        foreach ($patchTargets as $targetName) {
292
            $targetQueue = array();
293
294
            if (isset($patchFootprints[$targetName])) {
295
                $targetQueue = $patchFootprints[$targetName];
296
            }
297
298
            if (!isset($packages[$targetName])) {
299
                throw new \Vaimo\ComposerPatches\Exceptions\PackageNotFound(
300
                    sprintf(
301
                        'Unknown target "%s" found when checking patch changes for: %s',
302
                        $targetName,
303
                        implode(',', array_keys($targetQueue))
304
                    )
305
                );
306
            }
307
308
            $changesMap[$targetName] = $this->packageUtils->hasPatchChanges(
309
                $packages[$targetName],
310
                $targetQueue
311
            );
312
        }
313
314
        return array_keys(array_filter($changesMap));
315
    }
316
    
317
    private function processPatchesForPackage(Repository $repository, Package $package, array $patchesQueue)
318
    {
319
        $processIndentation = $this->logger->push('~');
320
321
        try {
322
            $appliedPatches = $this->packagePatchApplier->applyPatches($package, $patchesQueue);
323
324
            $this->patcherStateManager->registerAppliedPatches($repository, $appliedPatches);
325
326
            $this->logger->reset($processIndentation);
327
        } catch (\Vaimo\ComposerPatches\Exceptions\PatchFailureException $exception) {
328
            $this->logger->push();
329
            
330
            $this->logger->write('error', $exception->getMessage());
331
332
            $previousError = $exception->getPrevious();
333
            
334
            if ($previousError instanceof \Exception) {
0 ignored issues
show
introduced by
$previousError is always a sub-type of Exception.
Loading history...
335
                $this->outputGenerator->generateForException($previousError);
336
            }
337
338
            $failedPath = $exception->getFailedPatchPath();
339
340
            $paths = array_keys($patchesQueue);
341
            
342
            $failureIndex = array_search($failedPath, $paths);
343
344
            $appliedPatches = array();
345
346
            if (is_int($failureIndex)) {
347
                $appliedPaths = array_slice($paths, 0, $failureIndex);
348
                $appliedPatches = array_intersect_key($patchesQueue, array_flip($appliedPaths));
349
            }
350
351
            $this->patcherStateManager->registerAppliedPatches($repository, $appliedPatches);
352
353
            throw $exception;
354
        }
355
    }
356
    
357
    private function shouldAllowOutput(array $patches, array $removals)
358
    {
359
        return $this->outputStrategy->shouldAllowForPatches($patches)
360
            || $this->outputStrategy->shouldAllowForPatches($removals);
361
    }
362
}
363