PatchesApplier::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 18
nc 1
nop 9
dl 0
loc 33
rs 9.6666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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