Passed
Branch master (4a352c)
by Allan
04:38 queued 02:31
created

Applier::processOperations()   F

Complexity

Conditions 15
Paths 404

Size

Total Lines 93
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 51
nc 404
nop 4
dl 0
loc 93
rs 2.5777
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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\Patch\File;
7
8
use Vaimo\ComposerPatches\Config as PluginConfig;
9
10
class Applier
11
{
12
    /**
13
     * @var \Vaimo\ComposerPatches\Logger
14
     */
15
    private $logger;
16
    
17
    /**
18
     * @var \Vaimo\ComposerPatches\Shell
19
     */
20
    private $shell;
21
22
    /**
23
     * @var array
24
     */
25
    private $config;
26
27
    /**
28
     * @var \Vaimo\ComposerPatches\Utils\ConfigUtils
29
     */
30
    private $applierUtils;
31
32
    /**
33
     * @var \Vaimo\ComposerPatches\Utils\TemplateUtils
34
     */
35
    private $templateUtils;
36
37
    /**
38
     * @var array
39
     */
40
    private $resultCache;
41
    
42
    /**
43
     * @param \Vaimo\ComposerPatches\Logger $logger
44
     * @param array $config
45
     */
46
    public function __construct(
47
        \Vaimo\ComposerPatches\Logger $logger,
48
        array $config
49
    ) {
50
        $this->logger = $logger;
51
        $this->config = $config;
52
53
        $this->shell = new \Vaimo\ComposerPatches\Shell($logger);
54
        $this->applierUtils = new \Vaimo\ComposerPatches\Utils\ConfigUtils();
55
        $this->templateUtils = new \Vaimo\ComposerPatches\Utils\TemplateUtils();
56
    }
57
58
    public function applyFile($filename, $cwd, array $config = array())
59
    {
60
        $applierConfig = $this->applierUtils->mergeApplierConfig($this->config, array_filter($config));
61
        
62
        $applierConfig = $this->applierUtils->sortApplierConfig($applierConfig);
63
64
        $patchers = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_APPLIERS);
65
        $operations = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_OPERATIONS);
66
        $levels = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_LEVELS);
67
        $failureMessages = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_FAILURES);
68
69
        $sanityOperations = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_SANITY);
70
        
71
        try {
72
            $this->applierUtils->validateConfig($applierConfig);
73
        } catch (\Vaimo\ComposerPatches\Exceptions\ConfigValidationException $exception) {
74
            $this->logger->writeVerbose('error', $exception->getMessage());
75
        }
76
77
        $arguments = array(
78
            PluginConfig::PATCHER_ARG_FILE => $filename,
79
            PluginConfig::PATCHER_ARG_CWD => $cwd
80
        );
81
            
82
        $patcherName = $this->processOperations($patchers, $sanityOperations, $arguments);
83
        
84
        if (!$patcherName) {
85
            $message = sprintf(
86
                'None of the patch appliers seem to be available (tried: %s)',
87
                implode(', ', array_keys($patchers))
88
            );
89
            
90
            throw new \Vaimo\ComposerPatches\Exceptions\RuntimeException($message);
91
        }
92
93
        foreach ($levels as $patchLevel) {
94
            $arguments = array_replace(
95
                $arguments, 
96
                array(PluginConfig::PATCHER_ARG_LEVEL => $patchLevel)
97
            );
98
            
99
            $patcherName = $this->processOperations(
100
                $patchers,
101
                $operations,
102
                $arguments,
103
                $failureMessages
104
            );
105
            
106
            if (!$patcherName) {
107
                continue;
108
            }
109
110
            $this->logger->writeVerbose(
111
                'info',
112
                'SUCCESS with type=%s (p=%s)',
113
                array($patcherName, $patchLevel)
114
            );
115
116
            return;
117
        }
118
119
        throw new \Exception(
120
            sprintf('Cannot apply patch %s', $filename)
121
        );
122
    }
123
    
124
    private function processOperations($patchers, array $operations, array $args = array(), array $failures = array())
125
    {
126
        list($operationName, $operationCode) = array_fill(0, 4, 'UNKNOWN');
127
        
128
        $variableFormats = array(
129
            '{{%s}}' => array('escapeshellarg'),
130
            '[[%s]]' => array()
131
        );
132
133
        foreach ($patchers as $type => $patcher) {
134
            $result = true;
135
136
            if (!$patcher) {
137
                continue;
138
            }
139
140
            $operationResults[$type] = array_fill_keys(array_keys($operations), '');
141
142
            foreach ($operations as $operationCode => $operationName) {
143
                if (!isset($patcher[$operationCode])) {
144
                    continue;
145
                }
146
147
                $args = array_replace($args, $operationResults[$type]);
148
149
                $applierOperations = is_array($patcher[$operationCode])
150
                    ? $patcher[$operationCode]
151
                    : array($patcher[$operationCode]);
152
153
                foreach ($applierOperations as $operation) {
154
                    $passOnFailure = strpos($operation, '!') === 0;
155
                    $operation = ltrim($operation, '!');
156
157
                    $command = $this->templateUtils->compose($operation, $args, $variableFormats);
158
159
                    $cwd = $this->extractStringValue($args, PluginConfig::PATCHER_ARG_CWD);
160
                    
161
                    $resultKey = $cwd . ' | ' . $command;
162
163
                    if ($passOnFailure) {
164
                        $this->logger->writeVerbose(
165
                            \Vaimo\ComposerPatches\Logger::TYPE_NONE,
166
                            '<comment>***</comment> '
167
                            . 'The expected result to execution is a failure'
168
                            . '<comment>***</comment>'
169
                        );
170
                    }
171
172
                    if (!isset($this->resultCache[$resultKey])) {
173
                        $this->resultCache[$resultKey] = $this->shell->execute($command, $cwd);
174
                    }
175
176
                    list($result, $output) = $this->resultCache[$resultKey];
177
178
                    if ($result) {
179
                        $result = $this->scanOutputForFailures(
180
                            $output,
181
                            $this->extractArrayValue($failures, $operationCode)
182
                        );
183
                    }
184
185
                    if ($passOnFailure) {
186
                        $result = !$result;
187
                    }
188
189
                    if (!$result) {
190
                        continue;
191
                    }
192
193
                    $operationResults[$type][$operationCode] = $output;
194
195
                    break;
196
                }
197
198
                if (!$result) {
199
                    break;
200
                }
201
            }
202
203
            if ($result) {
204
                return $type;
205
            }
206
            
207
            $messageArgs = array(
208
                is_string($operationName) ? $operationName : $operationCode,
209
                $type,
210
                $this->extractStringValue($args, PluginConfig::PATCHER_ARG_LEVEL)
211
            );
212
            
213
            $this->logger->writeVerbose('warning', '%s (type=%s) failed with p=%s', $messageArgs);
214
        }
215
        
216
        return '';
217
    }
218
    
219
    private function scanOutputForFailures($output, array $failureMessages)
220
    {
221
        foreach ($failureMessages as $patternCode => $pattern) {
222
            if (!$pattern || !preg_match($pattern, $output)) {
223
                continue;
224
            }
225
226
            $this->logger->writeVerbose(
227
                'warning',
228
                sprintf(
229
                    'Success changed to FAILURE due to output analysis (%s): %s',
230
                    $patternCode,
231
                    $pattern
232
                )
233
            );
234
235
            return false;
236
        }
237
        
238
        return true;
239
    }
240
241
    private function extractArrayValue($data, $key)
242
    {
243
        return $this->extractValue($data, $key, array());
244
    }
245
246
    private function extractStringValue($data, $key)
247
    {
248
        return $this->extractValue($data, $key, '');
249
    }
250
251
    private function extractValue($data, $key, $default)
252
    {
253
        return isset($data[$key]) ? $data[$key] : $default;
254
    }
255
}
256