Passed
Push — master ( 168b91...023409 )
by Allan
02:32
created

ListCommand::execute()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 105
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 64
nc 24
nop 2
dl 0
loc 105
rs 8.1632
c 0
b 0
f 0

How to fix   Long Method   

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\Composer\Commands;
7
8
use Composer\Composer;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Vaimo\ComposerPatches\Patch\Definition as PatchDefinition;
12
13
use Vaimo\ComposerPatches\Interfaces\ListResolverInterface as ListResolver;
14
use Symfony\Component\Console\Input\InputOption;
15
use Vaimo\ComposerPatches\Repository\PatchesApplier\ListResolvers;
16
use Vaimo\ComposerPatches\Config;
17
use Vaimo\ComposerPatches\Patch\DefinitionList\LoaderComponents;
18
19
class ListCommand extends \Composer\Command\BaseCommand
20
{
21
    protected function configure()
22
    {
23
        parent::configure();
24
25
        $this->setName('patch:list');
26
27
        $this->setDescription('List all registered and eligible (based on project config) patches');
28
29
        $this->addArgument(
30
            'targets',
31
            \Symfony\Component\Console\Input\InputArgument::IS_ARRAY,
32
            'Packages for the patcher to target',
33
            array()
34
        );
35
36
        $this->addOption(
37
            '--no-dev',
38
            null,
39
            InputOption::VALUE_NONE,
40
            'Disables installation of require-dev packages'
41
        );
42
43
        $this->addOption(
44
            '--filter',
45
            null,
46
            InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
47
            'Mark certain patches with MATCH in the output list'
48
        );
49
50
        $this->addOption(
51
            '--excluded',
52
            null,
53
            InputOption::VALUE_NONE,
54
            'Include patches that have been ruled out based on some constraint mismatch'
55
        );
56
57
        $this->addOption(
58
            '--with-excludes',
59
            null,
60
            InputOption::VALUE_NONE,
61
            'Alias for \'excluded\' argument'
62
        );
63
64
        $this->addOption(
65
            '--with-affected',
66
            null,
67
            InputOption::VALUE_NONE,
68
            'Mark patches that would get re-applied when changed/new patches are added (due to package reinstall)'
69
        );
70
        
71
        $this->addOption(
72
            '--brief',
73
            null,
74
            InputOption::VALUE_NONE,
75
            'Show more compact output of the list (remove description, owner , etc)'
76
        );
77
        
78
        $this->addOption(
79
            '--status',
80
            null,
81
            InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
82
            'Match specific statuses (changed, new, applied, removed)'
83
        );
84
85
        $this->addOption(
86
            '--from-source',
87
            null,
88
            InputOption::VALUE_NONE,
89
            'Use latest information from package configurations in vendor folder'
90
        );
91
    }
92
93
    protected function execute(InputInterface $input, OutputInterface $output)
94
    {
95
        $composer = $this->getComposer();
96
97
        $isDevMode = !$input->getOption('no-dev');
98
        $withExcluded = $input->getOption('excluded') || $input->getOption('with-excludes');
99
        $withAffected = $input->getOption('with-affected');
100
        $beBrief = $input->getOption('brief');
101
        
102
        $filters = array(
103
            PatchDefinition::SOURCE => $input->getOption('filter'),
104
            PatchDefinition::TARGETS => $input->getArgument('targets')
105
        );
106
107
        $statusFilters = array_map('strtolower', $input->getOption('status'));
0 ignored issues
show
Bug introduced by
It seems like $input->getOption('status') can also be of type boolean and null and string; however, parameter $arr1 of array_map() does only seem to accept array, 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

107
        $statusFilters = array_map('strtolower', /** @scrutinizer ignore-type */ $input->getOption('status'));
Loading history...
108
        
109
        $pluginConfig = $this->createConfigWithEnabledSources($composer);
110
        
111
        
112
        $filteredPool = $this->createLoaderPool();
113
        
114
        $packageInfoResolver = new \Vaimo\ComposerPatches\Package\InfoResolver(
115
            $composer->getInstallationManager(),
116
            $composer->getConfig()->get(\Vaimo\ComposerPatches\Composer\ConfigKeys::VENDOR_DIR)
117
        );
118
        
119
        $unfilteredPool = $this->createLoaderPool(array(
120
            'constraints' => false,
121
            'platform' => false,
122
            'local-exclude' => false,
123
            'root-patch' => false,
124
            'global-exclude' => false,
125
            'targets-resolver' => new LoaderComponents\TargetsResolverComponent($packageInfoResolver, true)
126
        ));
127
        
128
        $listResolver = new ListResolvers\FilteredListResolver($filters);
129
        
130
        $loaderFactory = new \Vaimo\ComposerPatches\Factories\PatchesLoaderFactory($composer);
131
        
132
        $repository = $composer->getRepositoryManager()->getLocalRepository();
133
        
134
        $filteredLoader = $loaderFactory->create($filteredPool, $pluginConfig, $isDevMode);
135
        $filteredPatches = $filteredLoader->loadFromPackagesRepository($repository);
136
        
137
        $queueGenerator = $this->createQueueGenerator($composer, $pluginConfig, $listResolver);
138
139
        $repoStateGenerator = $this->createStateGenerator($composer);
140
        
141
        $repositoryState = $repoStateGenerator->generate($repository);
142
        
143
        $applyQueue = $queueGenerator->generateApplyQueue($filteredPatches, $repositoryState);
144
        $removeQueue = $queueGenerator->generateRemovalQueue($applyQueue, $repositoryState);
145
        $applyQueue = array_map('array_filter', $applyQueue);
146
147
        $patchListUtils = new \Vaimo\ComposerPatches\Utils\PatchListUtils();
148
        $patchListUpdater = new \Vaimo\ComposerPatches\Patch\DefinitionList\Updater();
149
150
        $filteredPatches = $this->composerFilteredPatchesList(
151
            $filteredPatches,
152
            $applyQueue,
153
            $removeQueue,
154
            $withAffected,
155
            $filters
156
        );
157
        
158
        $patches = array_filter($filteredPatches);
159
160
        $filterUtils = new \Vaimo\ComposerPatches\Utils\FilterUtils();
161
162
        $shouldAddExcludes = $withExcluded
163
            && (
164
                empty($statusFilters)
165
                || preg_match($filterUtils->composeRegex($statusFilters, '/'), 'excluded')
166
            );
167
        
168
        if ($shouldAddExcludes) {
169
            $unfilteredLoader = $loaderFactory->create($unfilteredPool, $pluginConfig, $isDevMode);
170
171
            $allPatches = $unfilteredLoader->loadFromPackagesRepository($repository);
172
            
173
            $patchesQueue = $listResolver->resolvePatchesQueue($allPatches);
174
            
175
            $excludedPatches = $patchListUpdater->updateStatuses(
176
                array_filter($patchListUtils->diffListsByPath($patchesQueue, $filteredPatches)),
177
                'excluded'
178
            );
179
180
            $patches = array_replace_recursive(
181
                $patches,
182
                $patchListUpdater->updateStatuses($excludedPatches, 'excluded')
183
            );
184
185
            array_walk($patches, function (array &$group) {
186
                ksort($group);
187
            }, $patches);
188
        }
189
        
190
        if ($beBrief) {
191
            $patches = $patchListUpdater->embedInfoToItems($patches, array(
192
                PatchDefinition::LABEL => false,
193
                PatchDefinition::OWNER => false
194
            ));
195
        }
196
        
197
        $this->generateOutput($output, $patches);
198
    }
199
    
200
    private function composerFilteredPatchesList($patches, $applyQueue, $removeQueue, $withAffected, $filters)
201
    {
202
        $hasFilers = (bool)array_filter($filters);
203
204
        $listResolver = new ListResolvers\FilteredListResolver($filters);
205
        
206
        $patchListUtils = new \Vaimo\ComposerPatches\Utils\PatchListUtils();
207
        $patchListUpdater = new \Vaimo\ComposerPatches\Patch\DefinitionList\Updater();
208
209
        $filteredPatches = $patchListUtils->mergeLists(
210
            $patches,
211
            $removeQueue
212
        );
213
214
        if ($withAffected) {
215
            $applyQueue = $patchListUpdater->embedInfoToItems(
216
                $applyQueue,
217
                array(PatchDefinition::STATUS => 'affected'),
218
                true
219
            );
220
        }
221
222
        $filteredPatches = $patchListUtils->mergeLists(
223
            $filteredPatches,
224
            $patchListUtils->intersectListsByName($applyQueue, $filteredPatches)
225
        );
226
227
        $filteredPatches = $patchListUpdater->embedInfoToItems(
228
            $filteredPatches,
229
            array(PatchDefinition::STATUS => 'applied'),
230
            true
231
        );
232
233
        if ($hasFilers) {
234
            $filteredPatches = $listResolver->resolvePatchesQueue($filteredPatches);
235
        }
236
237
        $filterUtils = new \Vaimo\ComposerPatches\Utils\FilterUtils();
238
239
        if (!empty($statusFilters)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $statusFilters does not exist. Did you maybe mean $statusFilter?
Loading history...
240
            $statusFilter = $filterUtils->composeRegex($statusFilters, '/');
241
242
            $filteredPatches = $patchListUtils->applyDefinitionKeyValueFilter(
243
                $filteredPatches,
244
                $statusFilter,
245
                PatchDefinition::STATUS
246
            );
247
        }
248
        
249
        return $filteredPatches;
250
    }
251
    
252
    private function createConfigWithEnabledSources(Composer $composer)
253
    {
254
        $configDefaults = new \Vaimo\ComposerPatches\Config\Defaults();
255
256
        $defaultValues = $configDefaults->getPatcherConfig();
257
258
        $sourceKeys = array_keys($defaultValues[Config::PATCHER_SOURCES]);
0 ignored issues
show
Bug introduced by
It seems like $defaultValues[Vaimo\Com...onfig::PATCHER_SOURCES] can also be of type boolean; however, parameter $input of array_keys() does only seem to accept array, 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

258
        $sourceKeys = array_keys(/** @scrutinizer ignore-type */ $defaultValues[Config::PATCHER_SOURCES]);
Loading history...
259
260
        $pluginConfig = array(
261
            Config::PATCHER_SOURCES => array_fill_keys($sourceKeys, true)
262
        );
263
264
        $configFactory = new \Vaimo\ComposerPatches\Factories\ConfigFactory($composer);
265
        
266
        return $configFactory->create(array($pluginConfig));
267
    }
268
    
269
    private function createStateGenerator(Composer $composer)
270
    {
271
        $packageCollector = new \Vaimo\ComposerPatches\Package\Collector(
272
            array($composer->getPackage())
273
        );
274
275
        return new \Vaimo\ComposerPatches\Repository\StateGenerator(
276
            $packageCollector
277
        );
278
    }
279
    
280
    private function createQueueGenerator(Composer $composer, Config $config, ListResolver $listResolver)
281
    {
282
        $stateAnalyserFactory = new \Vaimo\ComposerPatches\Factories\RepositoryStateAnalyserFactory(
283
            $composer
284
        );
285
        
286
        $changesListResolver = new ListResolvers\ChangesListResolver($listResolver);
287
288
        $stateAnalyser = $stateAnalyserFactory->create($config);
0 ignored issues
show
Unused Code introduced by
The call to Vaimo\ComposerPatches\Fa...alyserFactory::create() has too many arguments starting with $config. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

288
        /** @scrutinizer ignore-call */ 
289
        $stateAnalyser = $stateAnalyserFactory->create($config);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
289
        
290
        return new \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator(
291
            $changesListResolver,
292
            $stateAnalyser
293
        );
294
    }
295
    
296
    private function createLoaderPool(array $componentUpdates = array())
297
    {
298
        $componentPool = new \Vaimo\ComposerPatches\Patch\DefinitionList\Loader\ComponentPool(
299
            $this->getComposer(),
300
            $this->getIO()
301
        );
302
303
        foreach ($componentUpdates as $componentName => $replacement) {
304
            $componentPool->registerComponent($componentName, $replacement);
305
        }
306
        
307
        return $componentPool;
308
    }
309
    
310
    private function generateOutput(OutputInterface $output, array $list)
311
    {
312
        $statusConfig = new \Vaimo\ComposerPatches\Package\PatchApplier\StatusConfig();
313
314
        $statusDecorators = $statusConfig->getLabels();
315
        
316
        foreach ($list as $packageName => $patches) {
317
            $output->writeln(sprintf('<info>%s</info>', $packageName));
318
319
            foreach ($patches as $path => $info) {
320
                $status = isset($info[PatchDefinition::STATUS])
321
                    ? $info[PatchDefinition::STATUS]
322
                    : 'unknown';
323
324
                $statusLabel = sprintf(' [%s]', $statusDecorators[$status]);
325
                $owner = $info[PatchDefinition::OWNER];
326
                
327
                if ($owner === PatchDefinition::OWNER_UNKNOWN) {
328
                    $patchInfoLabel = sprintf('  ~ %s%s', $path, $statusLabel);
329
                } elseif ($owner) {
330
                    $patchInfoLabel = sprintf('  ~ <info>%s</info>: %s%s', $owner, $path, $statusLabel);
331
                } else {
332
                    $patchInfoLabel = sprintf('%s%s', $path, $statusLabel);
333
                }
334
335
                $output->writeln($patchInfoLabel);
336
337
                $descriptionLines = array_filter(
338
                    explode(PHP_EOL, $info[PatchDefinition::LABEL])
339
                );
340
                
341
                foreach ($descriptionLines as $line) {
342
                    $output->writeln(sprintf('    <comment>%s</comment>', $line));
343
                }
344
            }
345
346
            $output->writeln('');
347
        }
348
    }
349
}
350