Passed
Push — master ( b12c89...119ef1 )
by Allan
02:13
created

PatchCommand   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Importance

Changes 4
Bugs 2 Features 1
Metric Value
eloc 127
dl 0
loc 234
rs 10
c 4
b 2
f 1
wmc 24

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getOptionGraceful() 0 3 2
A configureEnvironmentForBehaviour() 0 6 1
A configure() 0 73 1
A resolveOutputTriggers() 0 20 3
A resolveActiveFilters() 0 14 3
A getBehaviourFlags() 0 8 2
A attachBehaviourToListResolver() 0 9 3
B execute() 0 58 6
A createListResolver() 0 13 3
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 Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Output\OutputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Input\InputArgument;
12
use Composer\Script\ScriptEvents;
13
14
use Vaimo\ComposerPatches\Patch\Definition as Patch;
15
use Vaimo\ComposerPatches\Patch\Definition as PatchDefinition;
16
17
use Vaimo\ComposerPatches\Repository\PatchesApplier\ListResolvers;
18
use Vaimo\ComposerPatches\Config;
19
use Vaimo\ComposerPatches\Interfaces\ListResolverInterface;
20
21
use Vaimo\ComposerPatches\Environment;
22
23
class PatchCommand extends \Composer\Command\BaseCommand
24
{
25
    protected function configure()
26
    {
27
        $this->setName('patch');
28
        $this->setDescription('Apply registered patches to current project');
29
30
        $this->addArgument(
31
            'targets',
32
            InputArgument::IS_ARRAY,
33
            'Packages for the patcher to target',
34
            array()
35
        );
36
37
        $this->addOption(
38
            '--redo',
39
            null,
40
            InputOption::VALUE_NONE,
41
            'Re-patch all packages or a specific package when targets defined'
42
        );
43
44
        $this->addOption(
45
            '--undo',
46
            null,
47
            InputOption::VALUE_NONE,
48
            'Remove all patches or a specific patch when targets defined'
49
        );
50
51
        $this->addOption(
52
            '--no-dev',
53
            null,
54
            InputOption::VALUE_NONE,
55
            'Disables installation of require-dev packages'
56
        );
57
58
        $this->addOption(
59
            '--filter',
60
            null,
61
            InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
62
            'Apply only those patch files/sources that match with provided filter'
63
        );
64
        
65
        $this->addOption(
66
            '--explicit',
67
            null,
68
            InputOption::VALUE_NONE,
69
            'Show information for every patch that gets re-applied (due to package reset)'
70
        );
71
72
        $this->addOption(
73
            '--show-reapplies',
74
            null,
75
            InputOption::VALUE_NONE,
76
            'Alias for \'explicit\' argument'
77
        );
78
        
79
        $this->addOption(
80
            '--from-source',
81
            null,
82
            InputOption::VALUE_NONE,
83
            'Apply patches based on information directly from packages in vendor folder'
84
        );
85
86
        $this->addOption(
87
            '--graceful',
88
            null,
89
            InputOption::VALUE_NONE,
90
            'Continue even when some patch fails to apply'
91
        );
92
93
        $this->addOption(
94
            '--force',
95
            null,
96
            InputOption::VALUE_NONE,
97
            'Force package reset even when it has local change'
98
        );
99
    }
100
    
101
    /**
102
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
103
     *
104
     * @param InputInterface $input
105
     * @param OutputInterface $output
106
     * @return int|void|null
107
     * @throws \Exception
108
     */
109
    protected function execute(InputInterface $input, OutputInterface $output)
110
    {
111
        $configDefaults = new \Vaimo\ComposerPatches\Config\Defaults();
112
        $defaults = $configDefaults->getPatcherConfig();
113
114
        $composer = $this->getComposer();
115
116
        $appIO = $this->getIO();
117
        
118
        $isDevMode = !$input->getOption('no-dev');
119
120
        $behaviourFlags = $this->getBehaviourFlags($input);
121
        $shouldUndo = !$behaviourFlags['redo'] && $behaviourFlags['undo'];
122
        
123
        $bootstrapFactory = new \Vaimo\ComposerPatches\Factories\BootstrapFactory($composer, $appIO);
124
125
        $configFactory = new \Vaimo\ComposerPatches\Factories\ConfigFactory($composer, array(
126
            Config::PATCHER_FORCE_REAPPLY => $behaviourFlags['redo'],
127
            Config::PATCHER_FROM_SOURCE => (bool)$input->getOption('from-source'),
128
            Config::PATCHER_GRACEFUL => (bool)$input->getOption('graceful')
129
                || $behaviourFlags['redo']
130
                || $behaviourFlags['undo'],
131
            Config::PATCHER_SOURCES => array_fill_keys(array_keys($defaults[Config::PATCHER_SOURCES]), true)
0 ignored issues
show
Bug introduced by
It seems like $defaults[Vaimo\Composer...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

131
            Config::PATCHER_SOURCES => array_fill_keys(array_keys(/** @scrutinizer ignore-type */ $defaults[Config::PATCHER_SOURCES]), true)
Loading history...
132
        ));
133
        
134
        $filters = $this->resolveActiveFilters($input, $behaviourFlags);
135
        
136
        $listResolver = $this->createListResolver($behaviourFlags, $filters);
137
        
138
        $this->configureEnvironmentForBehaviour($behaviourFlags);
139
140
        $outputTriggers = $this->resolveOutputTriggers($filters, $behaviourFlags);
141
        $outputStrategy = new \Vaimo\ComposerPatches\Strategies\OutputStrategy($outputTriggers);
142
        $bootstrap = $bootstrapFactory->create($configFactory, $listResolver, $outputStrategy);
143
144
        $runtimeUtils = new \Vaimo\ComposerPatches\Utils\RuntimeUtils();
145
        $lockSanitizer = new \Vaimo\ComposerPatches\Repository\Lock\Sanitizer($appIO);
146
        $repository = $composer->getRepositoryManager()->getLocalRepository();
147
        
148
        $result = $runtimeUtils->executeWithPostAction(
149
            function () use ($shouldUndo, $filters, $bootstrap, $isDevMode) {
150
                if ($shouldUndo && !array_filter($filters)) {
151
                    $bootstrap->stripPatches($isDevMode);
152
                    
153
                    return true;
154
                }
155
156
                return $bootstrap->applyPatches($isDevMode);
157
            },
158
            function () use ($repository, $lockSanitizer) {
159
                $repository->write();
160
                $lockSanitizer->sanitize();
161
            }
162
        );
163
164
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_INSTALL_CMD, $isDevMode);
165
166
        return (int)!$result;
167
    }
168
169
    protected function getBehaviourFlags(InputInterface $input)
170
    {
171
        return array(
172
            'redo' => $this->getOptionGraceful($input, 'redo'),
173
            'undo' => $this->getOptionGraceful($input, 'undo'),
174
            'force' => $this->getOptionGraceful($input, 'force'),
175
            'explicit' => $this->getOptionGraceful($input, 'explicit')
176
                || $this->getOptionGraceful($input, 'show-reapplies')
177
        );
178
    }
179
    
180
    private function getOptionGraceful(InputInterface $input, $name)
181
    {
182
        return $input->hasOption($name) && $input->getOption($name);
183
    }
184
    
185
    private function resolveActiveFilters(InputInterface $input, array $behaviourFlags)
186
    {
187
        $filters = array(
188
            Patch::SOURCE => $input->getOption('filter'),
189
            Patch::TARGETS => $input->getArgument('targets')
190
        );
191
192
        $hasFilers = (bool)array_filter($filters);
193
194
        if (!$hasFilers && $behaviourFlags['redo']) {
195
            $filters[Patch::SOURCE] = array('*');
196
        }
197
198
        return $filters;
199
    }
200
    
201
    private function configureEnvironmentForBehaviour(array $behaviourFlags)
202
    {
203
        $runtimeUtils = new \Vaimo\ComposerPatches\Utils\RuntimeUtils();
204
205
        $runtimeUtils->setEnvironmentValues(array(
206
            Environment::FORCE_RESET => (int)$behaviourFlags['force']
207
        ));
208
    }
209
    
210
    private function resolveOutputTriggers(array $filters, array $behaviourFlags)
211
    {
212
        $hasFilers = (bool)array_filter($filters);
213
214
        $isExplicit = $behaviourFlags['explicit'];
215
        
216
        if (!$hasFilers && $behaviourFlags['redo']) {
217
            $isExplicit = true;
218
        }
219
        
220
        $outputTriggerFlags = array(
221
            Patch::STATUS_NEW => !$hasFilers,
222
            Patch::STATUS_CHANGED => !$hasFilers,
223
            Patch::STATUS_MATCH => true,
224
            Patch::SOURCE => $isExplicit,
225
            Patch::URL => $isExplicit
226
        );
227
228
        return array_keys(
229
            array_filter($outputTriggerFlags)
230
        );
231
    }
232
    
233
    private function createListResolver(array $behaviourFlags, array $filters)
234
    {
235
        $listResolver = new ListResolvers\FilteredListResolver($filters);
236
237
        $isDefaultBehaviour = !$behaviourFlags['redo'] && !$behaviourFlags['undo'];
238
239
        $listResolver = $this->attachBehaviourToListResolver($listResolver, $behaviourFlags);
240
        
241
        if ($isDefaultBehaviour) {
242
            $listResolver = new ListResolvers\ChangesListResolver($listResolver);
243
        }
244
        
245
        return $listResolver;
246
    }
247
    
248
    private function attachBehaviourToListResolver(ListResolverInterface $listResolver, array $behaviourFlags)
249
    {
250
        $shouldUndo = !$behaviourFlags['redo'] && $behaviourFlags['undo'];
251
252
        if ($shouldUndo) {
253
            return new ListResolvers\InvertedListResolver($listResolver);
254
        }
255
256
        return new ListResolvers\InclusiveListResolver($listResolver);
257
    }
258
}
259