Passed
Push — master ( 5bb3b6...f2e35b )
by Allan
02:20
created

ListCommand   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 295
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 175
dl 0
loc 295
rs 10
c 0
b 0
f 0
wmc 20

5 Methods

Rating   Name   Duplication   Size   Complexity  
C execute() 0 152 9
A createLoaderPool() 0 12 2
A configure() 0 69 1
A createQueueGenerator() 0 13 1
B generateOutput() 0 37 7
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
        $configDefaults = new \Vaimo\ComposerPatches\Config\Defaults();
110
111
        $defaultValues = $configDefaults->getPatcherConfig();
112
113
        $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

113
        $sourceKeys = array_keys(/** @scrutinizer ignore-type */ $defaultValues[Config::PATCHER_SOURCES]);
Loading history...
114
115
        $pluginConfig = array(
116
            Config::PATCHER_SOURCES => array_fill_keys($sourceKeys, true)
117
        );
118
119
        $filterUtils = new \Vaimo\ComposerPatches\Utils\FilterUtils();
120
        $patchListUtils = new \Vaimo\ComposerPatches\Utils\PatchListUtils();
121
122
        $configFactory = new \Vaimo\ComposerPatches\Factories\ConfigFactory($composer);
123
        $configInstance = $configFactory->create(array($pluginConfig));
124
        
125
        $filteredPool = $this->createLoaderPool();
126
127
        $installationManager = $composer->getInstallationManager();
128
        $composerConfig = clone $composer->getConfig();
129
130
        $vendorRoot = $composerConfig->get(\Vaimo\ComposerPatches\Composer\ConfigKeys::VENDOR_DIR);
131
132
        $packageInfoResolver = new \Vaimo\ComposerPatches\Package\InfoResolver(
133
            $installationManager,
134
            $vendorRoot
135
        );
136
        
137
        $unfilteredPool = $this->createLoaderPool(array(
138
            'constraints' => false,
139
            'platform' => false,
140
            'local-exclude' => false,
141
            'root-patch' => false,
142
            'global-exclude' => false,
143
            'targets-resolver' => new LoaderComponents\TargetsResolverComponent($packageInfoResolver, true)
144
        ));
145
146
        $hasFilers = (bool)array_filter($filters);
147
        
148
        $listResolver = new ListResolvers\FilteredListResolver($filters);
149
        
150
        $loaderFactory = new \Vaimo\ComposerPatches\Factories\PatchesLoaderFactory($composer);
151
        $packageCollector = new \Vaimo\ComposerPatches\Package\Collector(
152
            array($composer->getPackage())
153
        );
154
        
155
        $repository = $composer->getRepositoryManager()->getLocalRepository();
156
        
157
        $filteredLoader = $loaderFactory->create($filteredPool, $configInstance, $isDevMode);
158
        $filteredPatches = $filteredLoader->loadFromPackagesRepository($repository);
159
        
160
        $queueGenerator = $this->createQueueGenerator($composer, $configInstance, $listResolver);
161
        
162
        $repoStateGenerator = new \Vaimo\ComposerPatches\Repository\StateGenerator(
163
            $packageCollector
164
        );
165
        
166
        $repositoryState = $repoStateGenerator->generate($repository);
167
        
168
        $applyQueue = $queueGenerator->generateApplyQueue($filteredPatches, $repositoryState);
169
        $removeQueue = $queueGenerator->generateRemovalQueue($applyQueue, $repositoryState);
170
        $applyQueue = array_map('array_filter', $applyQueue);
171
172
        $filteredPatches = $patchListUtils->mergeLists(
173
            $filteredPatches,
174
            $removeQueue
175
        );
176
177
        if ($withAffected) {
178
            $applyQueue = $patchListUtils->embedInfoToItems(
179
                $applyQueue,
180
                array(PatchDefinition::STATUS => 'affected'),
181
                true
182
            );
183
        }
184
        
185
        $filteredPatches = $patchListUtils->mergeLists(
186
            $filteredPatches,
187
            $patchListUtils->intersectListsByName($applyQueue, $filteredPatches)
188
        );
189
        
190
        $filteredPatches = $patchListUtils->embedInfoToItems(
191
            $filteredPatches,
192
            array(PatchDefinition::STATUS => 'applied'),
193
            true
194
        );
195
        
196
        if ($hasFilers) {
197
            $filteredPatches = $listResolver->resolvePatchesQueue($filteredPatches);
198
        }
199
        
200
        if ($statusFilters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $statusFilters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
201
            $statusFilter = $filterUtils->composeRegex($statusFilters, '/');
202
203
            $filteredPatches = $patchListUtils->applyDefinitionFilter(
204
                $filteredPatches,
205
                $statusFilter,
206
                PatchDefinition::STATUS
207
            );
208
        }
209
        
210
        $patches = array_filter($filteredPatches);
211
        
212
        $shouldAddExcludes = $withExcluded
213
            && (!$statusFilters || preg_match($filterUtils->composeRegex($statusFilters, '/'), 'excluded'));
0 ignored issues
show
Bug Best Practice introduced by
The expression $statusFilters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
214
        
215
        if ($shouldAddExcludes) {
216
            $unfilteredLoader = $loaderFactory->create($unfilteredPool, $configInstance, $isDevMode);
217
218
            $allPatches = $unfilteredLoader->loadFromPackagesRepository($repository);
219
            
220
            $patchesQueue = $listResolver->resolvePatchesQueue($allPatches);
221
            
222
            $excludedPatches = $patchListUtils->updateStatuses(
223
                array_filter($patchListUtils->diffListsByPath($patchesQueue, $filteredPatches)),
224
                'excluded'
225
            );
226
227
            $patches = array_replace_recursive(
228
                $patches,
229
                $patchListUtils->updateStatuses($excludedPatches, 'excluded')
230
            );
231
232
            array_walk($patches, function (array &$group) {
233
                ksort($group);
234
            }, $patches);
235
        }
236
        
237
        if ($beBrief) {
238
            $patches = $patchListUtils->embedInfoToItems($patches, array(
239
                PatchDefinition::LABEL => false,
240
                PatchDefinition::OWNER => false
241
            ));
242
        }
243
        
244
        $this->generateOutput($output, $patches);
245
    }
246
    
247
    private function createQueueGenerator(Composer $composer, Config $config, ListResolver $listResolver)
248
    {
249
        $stateAnalyserFactory = new \Vaimo\ComposerPatches\Factories\RepositoryStateAnalyserFactory(
250
            $composer
251
        );
252
        
253
        $changesListResolver = new ListResolvers\ChangesListResolver($listResolver);
254
255
        $stateAnalyser = $stateAnalyserFactory->create($config);
256
        
257
        return new \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator(
258
            $changesListResolver,
259
            $stateAnalyser
260
        );
261
    }
262
    
263
    private function createLoaderPool(array $componentUpdates = array())
264
    {
265
        $componentPool = new \Vaimo\ComposerPatches\Patch\DefinitionList\Loader\ComponentPool(
266
            $this->getComposer(),
267
            $this->getIO()
268
        );
269
270
        foreach ($componentUpdates as $componentName => $replacement) {
271
            $componentPool->registerComponent($componentName, $replacement);
272
        }
273
        
274
        return $componentPool;
275
    }
276
    
277
    private function generateOutput(OutputInterface $output, array $list)
278
    {
279
        $statusConfig = new \Vaimo\ComposerPatches\Package\PatchApplier\StatusConfig();
280
281
        $statusDecorators = $statusConfig->getLabels();
282
        
283
        foreach ($list as $packageName => $patches) {
284
            $output->writeln(sprintf('<info>%s</info>', $packageName));
285
286
            foreach ($patches as $path => $info) {
287
                $status = isset($info[PatchDefinition::STATUS])
288
                    ? $info[PatchDefinition::STATUS]
289
                    : 'unknown';
290
291
                $statusLabel = sprintf(' [%s]', $statusDecorators[$status]);
292
                $owner = $info[PatchDefinition::OWNER];
293
                
294
                if ($owner === PatchDefinition::OWNER_UNKNOWN) {
295
                    $patchInfoLabel = sprintf('  ~ %s%s', $path, $statusLabel);
296
                } elseif ($owner) {
297
                    $patchInfoLabel = sprintf('  ~ <info>%s</info>: %s%s', $owner, $path, $statusLabel);
298
                } else {
299
                    $patchInfoLabel = sprintf('%s%s', $path, $statusLabel);
300
                }
301
302
                $output->writeln($patchInfoLabel);
303
304
                $descriptionLines = array_filter(
305
                    explode(PHP_EOL, $info[PatchDefinition::LABEL])
306
                );
307
                
308
                foreach ($descriptionLines as $line) {
309
                    $output->writeln(sprintf('    <comment>%s</comment>', $line));
310
                }
311
            }
312
313
            $output->writeln('');
314
        }
315
    }
316
}
317