implicit conversion of array to boolean.
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 | use Vaimo\ComposerPatches\Repository\PatchesApplier\Operation as PatcherOperation; |
||
10 | |||
11 | class Applier |
||
12 | { |
||
13 | /** |
||
14 | * @var \Vaimo\ComposerPatches\Logger |
||
15 | */ |
||
16 | private $logger; |
||
17 | |||
18 | /** |
||
19 | * @var \Vaimo\ComposerPatches\Shell |
||
20 | */ |
||
21 | private $shell; |
||
22 | |||
23 | /** |
||
24 | * @var array |
||
25 | */ |
||
26 | private $config; |
||
27 | |||
28 | /** |
||
29 | * @var \Vaimo\ComposerPatches\Utils\ConfigUtils |
||
30 | */ |
||
31 | private $applierUtils; |
||
32 | |||
33 | /** |
||
34 | * @var \Vaimo\ComposerPatches\Utils\TemplateUtils |
||
35 | */ |
||
36 | private $templateUtils; |
||
37 | |||
38 | /** |
||
39 | * @var \Vaimo\ComposerPatches\Utils\DataUtils |
||
40 | */ |
||
41 | private $dataUtils; |
||
42 | |||
43 | /** |
||
44 | * @var array |
||
45 | */ |
||
46 | private $resultCache; |
||
47 | |||
48 | /** |
||
49 | * @param \Vaimo\ComposerPatches\Logger $logger |
||
50 | * @param array $config |
||
51 | */ |
||
52 | public function __construct( |
||
53 | \Vaimo\ComposerPatches\Logger $logger, |
||
54 | array $config |
||
55 | ) { |
||
56 | $this->logger = $logger; |
||
57 | $this->config = $config; |
||
58 | |||
59 | $this->shell = new \Vaimo\ComposerPatches\Shell($logger); |
||
60 | $this->applierUtils = new \Vaimo\ComposerPatches\Utils\ConfigUtils(); |
||
61 | $this->templateUtils = new \Vaimo\ComposerPatches\Utils\TemplateUtils(); |
||
62 | $this->dataUtils = new \Vaimo\ComposerPatches\Utils\DataUtils(); |
||
63 | } |
||
64 | |||
65 | public function applyFile($filename, $cwd, array $config = array()) |
||
66 | { |
||
67 | $applierConfig = $this->applierUtils->mergeApplierConfig($this->config, array_filter($config)); |
||
68 | |||
69 | $applierConfig = $this->applierUtils->sortApplierConfig($applierConfig); |
||
70 | |||
71 | $patchers = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_APPLIERS); |
||
72 | $operations = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_OPERATIONS); |
||
73 | $levels = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_LEVELS); |
||
74 | $failureMessages = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_FAILURES); |
||
75 | |||
76 | $sanityOperations = $this->extractArrayValue($applierConfig, PluginConfig::PATCHER_SANITY); |
||
77 | |||
78 | try { |
||
79 | $this->applierUtils->validateConfig($applierConfig); |
||
80 | } catch (\Vaimo\ComposerPatches\Exceptions\ConfigValidationException $exception) { |
||
81 | $this->logger->writeVerbose('error', $exception->getMessage()); |
||
82 | } |
||
83 | |||
84 | $arguments = array( |
||
85 | PluginConfig::PATCHER_ARG_FILE => $filename, |
||
86 | PluginConfig::PATCHER_ARG_CWD => $cwd |
||
87 | ); |
||
88 | |||
89 | $patcherName = $this->executeOperations($patchers, $sanityOperations, $arguments); |
||
90 | |||
91 | if (!$patcherName) { |
||
92 | $message = sprintf( |
||
93 | 'None of the patch appliers seem to be available (tried: %s)', |
||
94 | implode(', ', array_keys($patchers)) |
||
95 | ); |
||
96 | |||
97 | throw new \Vaimo\ComposerPatches\Exceptions\RuntimeException($message); |
||
98 | } |
||
99 | |||
100 | $errorGroups = array(); |
||
101 | |||
102 | foreach ($levels as $patchLevel) { |
||
103 | $arguments = array_replace( |
||
104 | $arguments, |
||
105 | array(PluginConfig::PATCHER_ARG_LEVEL => $patchLevel) |
||
106 | ); |
||
107 | |||
108 | try { |
||
109 | $patcherName = $this->executeOperations($patchers, $operations, $arguments, $failureMessages); |
||
110 | } catch (\Vaimo\ComposerPatches\Exceptions\ApplierFailure $exception) { |
||
111 | $errorGroups[] = $exception->getErrors(); |
||
112 | continue; |
||
113 | } |
||
114 | |||
115 | $this->logger->writeVerbose( |
||
116 | 'info', |
||
117 | 'SUCCESS with type=%s (p=%s)', |
||
118 | array($patcherName, $patchLevel) |
||
119 | ); |
||
120 | |||
121 | return; |
||
122 | } |
||
123 | |||
124 | $failure = new \Vaimo\ComposerPatches\Exceptions\ApplierFailure( |
||
125 | sprintf('Cannot apply patch %s', $filename) |
||
126 | ); |
||
127 | |||
128 | $failure->setErrors( |
||
129 | array_reduce($errorGroups, 'array_merge_recursive', array()) |
||
130 | ); |
||
131 | |||
132 | throw $failure; |
||
133 | } |
||
134 | |||
135 | private function executeOperations( |
||
136 | $patchers, |
||
137 | array $operations, |
||
138 | array $args = array(), |
||
139 | array $failures = array() |
||
140 | ) { |
||
141 | $outputRecords = array(); |
||
142 | |||
143 | foreach ($patchers as $type => $patcher) { |
||
144 | if (!$patcher) { |
||
145 | continue; |
||
146 | } |
||
147 | |||
148 | try { |
||
149 | return $this->processOperationItems($patcher, $operations, $args, $failures); |
||
150 | } catch (\Vaimo\ComposerPatches\Exceptions\OperationFailure $exception) { |
||
151 | $operationReference = is_string($exception->getMessage()) |
||
152 | ? $exception->getMessage() |
||
153 | : PatcherOperation::TYPE_UNKNOWN; |
||
154 | |||
155 | $outputRecords[$type] = $exception->getOutput(); |
||
156 | |||
157 | $messageArgs = array( |
||
158 | strtoupper($operationReference), |
||
159 | $type, |
||
160 | $this->extractStringValue($args, PluginConfig::PATCHER_ARG_LEVEL) |
||
161 | ); |
||
162 | |||
163 | $this->logger->writeVerbose('warning', '%s (type=%s) failed with p=%s', $messageArgs); |
||
164 | } |
||
165 | } |
||
166 | |||
167 | $messages = $this->collectErrors($outputRecords, array( |
||
168 | 'failed', |
||
169 | 'unexpected', |
||
170 | 'malformed', |
||
171 | 'error', |
||
172 | 'corrupt', |
||
173 | 'can\'t find file', |
||
174 | 'patch unexpectedly ends', |
||
175 | 'due to output analysis', |
||
176 | 'no file to patch' |
||
177 | )); |
||
178 | |||
179 | $failure = new \Vaimo\ComposerPatches\Exceptions\ApplierFailure(); |
||
180 | |||
181 | $failure->setErrors($messages); |
||
182 | |||
183 | throw $failure; |
||
184 | } |
||
185 | |||
186 | private function collectErrors(array $outputRecords, array $filters) |
||
187 | { |
||
188 | $pathMarker = '\|\+\+\+\s(.*)'; |
||
189 | |||
190 | $errorMatcher = sprintf( |
||
191 | '/(%s)/i', |
||
192 | implode('|', array_merge($filters, array($pathMarker))) |
||
193 | ); |
||
194 | |||
195 | $pathMatcher = sprintf('/^%s/i', $pathMarker); |
||
196 | |||
197 | $result = array(); |
||
198 | |||
199 | foreach ($outputRecords as $code => $output) { |
||
200 | $result[$code] = $this->dataUtils->listToGroups( |
||
201 | preg_grep($errorMatcher, explode(PHP_EOL, $output)), |
||
202 | $pathMatcher |
||
203 | ); |
||
204 | } |
||
205 | |||
206 | return $result; |
||
207 | } |
||
208 | |||
209 | private function processOperationItems($patcher, $operations, $args, $failures) |
||
210 | { |
||
211 | $operationResults = array_fill_keys(array_keys($operations), ''); |
||
212 | |||
213 | $result = true; |
||
214 | |||
215 | foreach (array_keys($operations) as $operationCode) { |
||
216 | if (!isset($patcher[$operationCode])) { |
||
217 | continue; |
||
218 | } |
||
219 | |||
220 | $args = array_replace($args, $operationResults); |
||
221 | |||
222 | $applierOperations = is_array($patcher[$operationCode]) |
||
223 | ? $patcher[$operationCode] |
||
224 | : array($patcher[$operationCode]); |
||
225 | |||
226 | |||
227 | $operationFailures = $this->extractArrayValue($failures, $operationCode); |
||
228 | |||
229 | list($result, $output) = $this->resolveOperationOutput( |
||
230 | $applierOperations, |
||
231 | $args, |
||
232 | $operationFailures |
||
233 | ); |
||
234 | |||
235 | if ($output !== false) { |
||
236 | $operationResults[$operationCode] = $output; |
||
237 | } |
||
238 | |||
239 | if ($result) { |
||
240 | continue; |
||
241 | } |
||
242 | |||
243 | $failure = new \Vaimo\ComposerPatches\Exceptions\OperationFailure($operationCode); |
||
244 | |||
245 | $failure->setOutput($output); |
||
246 | |||
247 | throw $failure; |
||
248 | } |
||
249 | |||
250 | return $result; |
||
251 | } |
||
252 | |||
253 | private function resolveOperationOutput($applierOperations, $args, $operationFailures) |
||
254 | { |
||
255 | $variableFormats = array( |
||
256 | '{{%s}}' => array('escapeshellarg'), |
||
257 | '[[%s]]' => array() |
||
258 | ); |
||
259 | |||
260 | $output = ''; |
||
261 | |||
262 | foreach ($applierOperations as $operation) { |
||
263 | $passOnFailure = strpos($operation, '!') === 0; |
||
264 | $operation = ltrim($operation, '!'); |
||
265 | |||
266 | $command = $this->templateUtils->compose($operation, $args, $variableFormats); |
||
267 | |||
268 | $cwd = $this->extractStringValue($args, PluginConfig::PATCHER_ARG_CWD); |
||
269 | |||
270 | $resultKey = sprintf('%s | %s', $cwd, $command); |
||
271 | |||
272 | if ($passOnFailure) { |
||
273 | $this->logger->writeVerbose( |
||
274 | \Vaimo\ComposerPatches\Logger::TYPE_NONE, |
||
275 | '<comment>***</comment> ' |
||
276 | . 'The expected result to execution is a failure' |
||
277 | . '<comment>***</comment>' |
||
278 | ); |
||
279 | } |
||
280 | |||
281 | if (isset($this->resultCache[$resultKey])) { |
||
282 | $this->logger->writeVerbose( |
||
283 | \Vaimo\ComposerPatches\Logger::TYPE_NONE, |
||
284 | sprintf('(using cached result for: %s = %s)', $command, reset($this->resultCache[$resultKey])) |
||
285 | ); |
||
286 | } |
||
287 | |||
288 | if (!isset($this->resultCache[$resultKey])) { |
||
289 | $this->resultCache[$resultKey] = $this->shell->execute($command, $cwd); |
||
290 | } |
||
291 | |||
292 | list($result, $output) = $this->resultCache[$resultKey]; |
||
293 | |||
294 | if ($result) { |
||
295 | $result = $this->scanOutputForFailures($output, $operationFailures); |
||
296 | } |
||
297 | |||
298 | if ($passOnFailure) { |
||
299 | $result = !$result; |
||
300 | } |
||
301 | |||
302 | if (!$result) { |
||
303 | continue; |
||
304 | } |
||
305 | |||
306 | return array($result, $output); |
||
307 | } |
||
308 | |||
309 | return array(false, $output); |
||
310 | } |
||
311 | |||
312 | private function scanOutputForFailures($output, array $failureMessages) |
||
313 | { |
||
314 | $pathMarker = '\|\+\+\+\s(.*)'; |
||
315 | $pathMatcher = sprintf('/^%s/', $pathMarker); |
||
316 | |||
317 | $patternsWithResults = array_filter($failureMessages, function ($pattern) use ($output) { |
||
318 | return $pattern && preg_match($pattern, $output); |
||
319 | }); |
||
320 | |||
321 | if (!$patternsWithResults) { |
||
0 ignored issues
–
show
|
|||
322 | return true; |
||
323 | } |
||
324 | |||
325 | $lines = explode(PHP_EOL, $output); |
||
326 | |||
327 | $matches = array(); |
||
328 | |||
329 | foreach ($lines as $line) { |
||
330 | if (preg_match($pathMatcher, $line)) { |
||
331 | $matches[] = $line; |
||
332 | |||
333 | continue; |
||
334 | } |
||
335 | |||
336 | foreach ($patternsWithResults as $patternCode => $pattern) { |
||
337 | if (!preg_match($pattern, $line)) { |
||
338 | continue; |
||
339 | } |
||
340 | |||
341 | $message = sprintf( |
||
342 | 'Success changed to FAILURE due to output analysis (%s)', |
||
343 | $patternCode |
||
344 | ); |
||
345 | |||
346 | $matches[] = $message; |
||
347 | |||
348 | $this->logger->writeVerbose( |
||
349 | 'warning', |
||
350 | sprintf('%s: %s', $message, $pattern) |
||
351 | ); |
||
352 | } |
||
353 | } |
||
354 | |||
355 | $failure = new \Vaimo\ComposerPatches\Exceptions\OperationFailure('Output analysis failed'); |
||
356 | |||
357 | $failure->setOutput( |
||
358 | implode(PHP_EOL, $matches) |
||
359 | ); |
||
360 | |||
361 | throw $failure; |
||
362 | } |
||
363 | |||
364 | private function extractArrayValue($data, $key) |
||
365 | { |
||
366 | return $this->extractValue($data, $key, array()); |
||
367 | } |
||
368 | |||
369 | private function extractStringValue($data, $key) |
||
370 | { |
||
371 | return $this->extractValue($data, $key, ''); |
||
372 | } |
||
373 | |||
374 | private function extractValue($data, $key, $default) |
||
375 | { |
||
376 | return isset($data[$key]) ? $data[$key] : $default; |
||
377 | } |
||
378 | } |
||
379 |
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.