Passed
Push — master ( f2e35b...cd03c9 )
by Allan
04:05 queued 15s
created

ListCommand::createConfigWithEnabledSources()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 1
dl 0
loc 15
rs 10
c 0
b 0
f 0
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
        $hasFilers = (bool)array_filter($filters);
129
        
130
        $listResolver = new ListResolvers\FilteredListResolver($filters);
131
        
132
        $loaderFactory = new \Vaimo\ComposerPatches\Factories\PatchesLoaderFactory($composer);
133
        
134
        $repository = $composer->getRepositoryManager()->getLocalRepository();
135
        
136
        $filteredLoader = $loaderFactory->create($filteredPool, $pluginConfig, $isDevMode);
137
        $filteredPatches = $filteredLoader->loadFromPackagesRepository($repository);
138
        
139
        $queueGenerator = $this->createQueueGenerator($composer, $pluginConfig, $listResolver);
140
141
        $repoStateGenerator = $this->createStateGenerator($composer);
142
        
143
        $repositoryState = $repoStateGenerator->generate($repository);
144
        
145
        $applyQueue = $queueGenerator->generateApplyQueue($filteredPatches, $repositoryState);
146
        $removeQueue = $queueGenerator->generateRemovalQueue($applyQueue, $repositoryState);
147
        $applyQueue = array_map('array_filter', $applyQueue);
148
149
        $patchListUtils = new \Vaimo\ComposerPatches\Utils\PatchListUtils();
150
151
        $filteredPatches = $patchListUtils->mergeLists(
152
            $filteredPatches,
153
            $removeQueue
154
        );
155
156
        if ($withAffected) {
157
            $applyQueue = $patchListUtils->embedInfoToItems(
158
                $applyQueue,
159
                array(PatchDefinition::STATUS => 'affected'),
160
                true
161
            );
162
        }
163
        
164
        $filteredPatches = $patchListUtils->mergeLists(
165
            $filteredPatches,
166
            $patchListUtils->intersectListsByName($applyQueue, $filteredPatches)
167
        );
168
        
169
        $filteredPatches = $patchListUtils->embedInfoToItems(
170
            $filteredPatches,
171
            array(PatchDefinition::STATUS => 'applied'),
172
            true
173
        );
174
        
175
        if ($hasFilers) {
176
            $filteredPatches = $listResolver->resolvePatchesQueue($filteredPatches);
177
        }
178
179
        $filterUtils = new \Vaimo\ComposerPatches\Utils\FilterUtils();
180
181
        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...
182
            $statusFilter = $filterUtils->composeRegex($statusFilters, '/');
183
184
            $filteredPatches = $patchListUtils->applyDefinitionFilter(
185
                $filteredPatches,
186
                $statusFilter,
187
                PatchDefinition::STATUS
188
            );
189
        }
190
        
191
        $patches = array_filter($filteredPatches);
192
        
193
        $shouldAddExcludes = $withExcluded
194
            && (!$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...
195
        
196
        if ($shouldAddExcludes) {
197
            $unfilteredLoader = $loaderFactory->create($unfilteredPool, $pluginConfig, $isDevMode);
198
199
            $allPatches = $unfilteredLoader->loadFromPackagesRepository($repository);
200
            
201
            $patchesQueue = $listResolver->resolvePatchesQueue($allPatches);
202
            
203
            $excludedPatches = $patchListUtils->updateStatuses(
204
                array_filter($patchListUtils->diffListsByPath($patchesQueue, $filteredPatches)),
205
                'excluded'
206
            );
207
208
            $patches = array_replace_recursive(
209
                $patches,
210
                $patchListUtils->updateStatuses($excludedPatches, 'excluded')
211
            );
212
213
            array_walk($patches, function (array &$group) {
214
                ksort($group);
215
            }, $patches);
216
        }
217
        
218
        if ($beBrief) {
219
            $patches = $patchListUtils->embedInfoToItems($patches, array(
220
                PatchDefinition::LABEL => false,
221
                PatchDefinition::OWNER => false
222
            ));
223
        }
224
        
225
        $this->generateOutput($output, $patches);
226
    }
227
    
228
    private function resolvePatchesList()
0 ignored issues
show
Unused Code introduced by
The method resolvePatchesList() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
229
    {
230
        
231
    }
232
    
233
    private function createConfigWithEnabledSources(Composer $composer)
234
    {
235
        $configDefaults = new \Vaimo\ComposerPatches\Config\Defaults();
236
237
        $defaultValues = $configDefaults->getPatcherConfig();
238
239
        $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

239
        $sourceKeys = array_keys(/** @scrutinizer ignore-type */ $defaultValues[Config::PATCHER_SOURCES]);
Loading history...
240
241
        $pluginConfig = array(
242
            Config::PATCHER_SOURCES => array_fill_keys($sourceKeys, true)
243
        );
244
245
        $configFactory = new \Vaimo\ComposerPatches\Factories\ConfigFactory($composer);
246
        
247
        return $configFactory->create(array($pluginConfig));
248
    }
249
    
250
    private function createStateGenerator(Composer $composer)
251
    {
252
        $packageCollector = new \Vaimo\ComposerPatches\Package\Collector(
253
            array($composer->getPackage())
254
        );
255
256
        return new \Vaimo\ComposerPatches\Repository\StateGenerator(
257
            $packageCollector
258
        );
259
    }
260
    
261
    private function createQueueGenerator(Composer $composer, Config $config, ListResolver $listResolver)
262
    {
263
        $stateAnalyserFactory = new \Vaimo\ComposerPatches\Factories\RepositoryStateAnalyserFactory(
264
            $composer
265
        );
266
        
267
        $changesListResolver = new ListResolvers\ChangesListResolver($listResolver);
268
269
        $stateAnalyser = $stateAnalyserFactory->create($config);
270
        
271
        return new \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator(
272
            $changesListResolver,
273
            $stateAnalyser
274
        );
275
    }
276
    
277
    private function createLoaderPool(array $componentUpdates = array())
278
    {
279
        $componentPool = new \Vaimo\ComposerPatches\Patch\DefinitionList\Loader\ComponentPool(
280
            $this->getComposer(),
281
            $this->getIO()
282
        );
283
284
        foreach ($componentUpdates as $componentName => $replacement) {
285
            $componentPool->registerComponent($componentName, $replacement);
286
        }
287
        
288
        return $componentPool;
289
    }
290
    
291
    private function generateOutput(OutputInterface $output, array $list)
292
    {
293
        $statusConfig = new \Vaimo\ComposerPatches\Package\PatchApplier\StatusConfig();
294
295
        $statusDecorators = $statusConfig->getLabels();
296
        
297
        foreach ($list as $packageName => $patches) {
298
            $output->writeln(sprintf('<info>%s</info>', $packageName));
299
300
            foreach ($patches as $path => $info) {
301
                $status = isset($info[PatchDefinition::STATUS])
302
                    ? $info[PatchDefinition::STATUS]
303
                    : 'unknown';
304
305
                $statusLabel = sprintf(' [%s]', $statusDecorators[$status]);
306
                $owner = $info[PatchDefinition::OWNER];
307
                
308
                if ($owner === PatchDefinition::OWNER_UNKNOWN) {
309
                    $patchInfoLabel = sprintf('  ~ %s%s', $path, $statusLabel);
310
                } elseif ($owner) {
311
                    $patchInfoLabel = sprintf('  ~ <info>%s</info>: %s%s', $owner, $path, $statusLabel);
312
                } else {
313
                    $patchInfoLabel = sprintf('%s%s', $path, $statusLabel);
314
                }
315
316
                $output->writeln($patchInfoLabel);
317
318
                $descriptionLines = array_filter(
319
                    explode(PHP_EOL, $info[PatchDefinition::LABEL])
320
                );
321
                
322
                foreach ($descriptionLines as $line) {
323
                    $output->writeln(sprintf('    <comment>%s</comment>', $line));
324
                }
325
            }
326
327
            $output->writeln('');
328
        }
329
    }
330
}
331