Passed
Push — master ( b3a69d...79ebcc )
by Allan
02:21 queued 11s
created

PatchesSearch::createPatchDefinitions()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 16
nc 4
nop 2
dl 0
loc 30
rs 9.7333
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\Patch\SourceLoaders;
7
8
use Vaimo\ComposerPatches\Patch\Definition as PatchDefinition;
9
use Vaimo\ComposerPatches\Config as PluginConfig;
10
11
class PatchesSearch implements \Vaimo\ComposerPatches\Interfaces\PatchSourceLoaderInterface
12
{
13
    /**
14
     * @var \Composer\Installer\InstallationManager
15
     */
16
    private $installationManager;
17
18
    /**
19
     * @var bool
20
     */
21
    private $devMode;
22
23
    /**
24
     * @var \Vaimo\ComposerPatches\Patch\File\Loader
25
     */
26
    private $patchFileLoader;
27
28
    /**
29
     * @var \Vaimo\ComposerPatches\Patch\File\Analyser
30
     */
31
    private $fileAnalyser;
32
33
    /**
34
     * @var \Vaimo\ComposerPatches\Patch\File\Header\Parser
35
     */
36
    private $patchHeaderParser;
37
38
    /**
39
     * @var \Vaimo\ComposerPatches\Utils\FileSystemUtils
40
     */
41
    private $fileSystemUtils;
42
43
    /**
44
     * @var array
45
     */
46
    private $tagAliases;
47
48
    /**
49
     * @var array
50
     */
51
    private $localTypes;
52
53
    /**
54
     * @var array
55
     */
56
    private $devModeTypes;
57
58
    /**
59
     * @var array
60
     */
61
    private $bundledModeTypes;
62
63
    /**
64
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
65
     *
66
     * @param \Composer\Installer\InstallationManager $installationManager
67
     * @param bool $devMode
68
     */
69
    public function __construct(
70
        \Composer\Installer\InstallationManager $installationManager,
71
        $devMode = false
72
    ) {
73
        $this->installationManager = $installationManager;
74
        $this->devMode = $devMode;
75
76
        $this->patchFileLoader = new \Vaimo\ComposerPatches\Patch\File\Loader();
77
        $this->fileAnalyser = new \Vaimo\ComposerPatches\Patch\File\Analyser();
78
79
        $this->patchHeaderParser = new \Vaimo\ComposerPatches\Patch\File\Header\Parser();
80
        $this->fileSystemUtils = new \Vaimo\ComposerPatches\Utils\FileSystemUtils();
81
82
        $this->tagAliases = array(
83
            PatchDefinition::LABEL => array('desc', 'description', 'reason'),
84
            PatchDefinition::ISSUE => array('ticket', 'issues', 'tickets'),
85
            PatchDefinition::VERSION => array('constraint'),
86
            PatchDefinition::PACKAGE => array('target', 'module', 'targets'),
87
            PatchDefinition::LINK => array('links', 'reference', 'ref', 'url'),
88
            PatchDefinition::TYPE => array('mode')
89
        );
90
91
        $this->localTypes = array('local', 'root');
92
        $this->devModeTypes = array('developer', 'dev', 'development', 'develop');
93
        $this->bundledModeTypes = array('bundle', 'bundled', 'merged', 'multiple', 'multi', 'group');
94
    }
95
96
    public function load(\Composer\Package\PackageInterface $package, $source)
97
    {
98
        if (!is_array($source)) {
99
            $source = array($source);
100
        }
101
102
        $basePath = $this->getInstallPath($package);
103
        $results = array();
104
105
        foreach ($source as $item) {
106
            $paths = $this->fileSystemUtils->collectFilePathsRecursively(
107
                $basePath . DIRECTORY_SEPARATOR . $item,
108
                sprintf('/%s/i', PluginConfig::PATCH_FILE_REGEX_MATCHER)
109
            );
110
111
            $results[] = $this->createPatchDefinitions($basePath, $paths);
112
        }
113
114
        return $results;
115
    }
116
117
    private function createPatchDefinitions($basePath, array $paths)
118
    {
119
        $groups = array();
120
        $basePathLength = strlen($basePath);
121
122
        foreach ($paths as $path) {
123
            $contents = $this->patchFileLoader->loadWithNormalizedLineEndings($path);
124
125
            $definition = $this->createDefinitionItem($contents, array(
126
                PatchDefinition::PATH => $path,
127
                PatchDefinition::SOURCE => trim(
128
                    substr($path, $basePathLength),
129
                    DIRECTORY_SEPARATOR
130
                )
131
            ));
132
133
            if (!isset($definition[PatchDefinition::TARGET])) {
134
                continue;
135
            }
136
137
            $target = $definition[PatchDefinition::TARGET];
138
139
            if (!isset($groups[$target])) {
140
                $groups[$target] = array();
141
            }
142
143
            $groups[$target][] = $definition;
144
        }
145
146
        return $groups;
147
    }
148
149
    private function getInstallPath(\Composer\Package\PackageInterface $package)
150
    {
151
        if ($package instanceof \Composer\Package\RootPackage) {
152
            return getcwd();
153
        }
154
155
        return $this->installationManager->getInstallPath($package);
156
    }
157
158
    private function createDefinitionItem($contents, array $values = array())
159
    {
160
        $header = $this->fileAnalyser->getHeader($contents);
161
162
        $data = $this->applyAliases(
163
            $this->patchHeaderParser->parseContents($header),
164
            $this->tagAliases
165
        );
166
167
        list($target, $depends, $flags) = $this->resolveBaseInfo($data);
168
169
        if (array_intersect_key($flags, array_flip($this->bundledModeTypes))) {
170
            $target = PatchDefinition::BUNDLE_TARGET;
171
        }
172
173
        if (!$target) {
174
            return array();
175
        }
176
177
        if (array_intersect_key($flags, array_flip($this->localTypes))) {
178
            $values[PatchDefinition::LOCAL] = true;
179
        }
180
181
        if (!$this->devMode && array_intersect_key($flags, array_flip($this->devModeTypes))) {
182
            $data[PatchDefinition::SKIP] = true;
183
        }
184
185
        return array_replace(array(
186
            PatchDefinition::LABEL => implode(
187
                PHP_EOL,
188
                isset($data[PatchDefinition::LABEL]) ? $data[PatchDefinition::LABEL] : array('')
189
            ),
190
            PatchDefinition::TARGET => $target,
191
            PatchDefinition::CWD => $this->extractSingleValue($data, PatchDefinition::CWD),
192
            PatchDefinition::TARGETS => $this->extractValueList(
193
                $data,
194
                PatchDefinition::TARGETS,
195
                array($target)
196
            ),
197
            PatchDefinition::DEPENDS => $depends,
198
            PatchDefinition::SKIP => isset($data[PatchDefinition::SKIP]),
199
            PatchDefinition::AFTER => $this->extractValueList($data, PatchDefinition::AFTER),
200
            PatchDefinition::ISSUE => $this->extractSingleValue($data, PatchDefinition::ISSUE),
201
            PatchDefinition::LINK => $this->extractSingleValue($data, PatchDefinition::LINK),
202
            PatchDefinition::LEVEL => $this->extractSingleValue($data, PatchDefinition::LEVEL),
203
            PatchDefinition::CATEGORY => $this->extractSingleValue($data, PatchDefinition::CATEGORY)
204
        ), $values);
205
    }
206
207
    private function resolveBaseInfo(array $data)
208
    {
209
        $target = false;
210
211
        $package = $this->extractSingleValue($data, PatchDefinition::PACKAGE);
212
        $depends = $this->extractSingleValue($data, PatchDefinition::DEPENDS);
213
        $version = $this->extractSingleValue($data, PatchDefinition::VERSION, '>=0.0.0');
214
215
        if (strpos($version, ':') !== false) {
216
            $valueParts = explode(':', $version);
217
218
            $depends = trim(array_shift($valueParts));
219
            $version = trim(implode(':', $valueParts));
220
        }
221
222
        if (strpos($package, ':') !== false) {
223
            $valueParts = explode(':', $package);
224
225
            $package = trim(array_shift($valueParts));
226
            $version = trim(implode(':', $valueParts));
227
        }
228
229
        if ($package) {
230
            $target = $package;
231
        }
232
233
        if (!$target && $depends) {
234
            $target = $depends;
235
        }
236
237
        if (!$depends && $target) {
238
            $depends = $target;
239
        }
240
241
        $dependsList = array_merge(
242
            array(sprintf('%s:%s', $package, $version)),
243
            array(sprintf('%s:%s', $depends, $version)),
0 ignored issues
show
Bug introduced by
It seems like $depends can also be of type false; however, parameter $values of sprintf() does only seem to accept double|integer|string, 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

243
            array(sprintf('%s:%s', /** @scrutinizer ignore-type */ $depends, $version)),
Loading history...
244
            $this->extractValueList($data, PatchDefinition::DEPENDS)
245
        );
246
247
        $patchTypeFlags = array_fill_keys(
248
            explode(
249
                PatchDefinition::TYPE_SEPARATOR,
250
                $this->extractSingleValue($data, PatchDefinition::TYPE)
251
            ),
252
            true
253
        );
254
255
        return array(
256
            $target,
257
            $this->normalizeDependencies($dependsList),
258
            $patchTypeFlags
259
        );
260
    }
261
262
    private function normalizeDependencies($dependsList)
263
    {
264
        $dependsNormalized = array_map(
265
            function ($item) {
266
                $valueParts = explode(':', $item);
267
268
                return array(
269
                    trim(array_shift($valueParts)) => trim(array_shift($valueParts)) ?: '>=0.0.0'
270
                );
271
            },
272
            array_unique($dependsList)
273
        );
274
275
        $dependsNormalized = array_reduce(
276
            $dependsNormalized,
277
            'array_replace',
278
            array()
279
        );
280
281
        return array_filter(
282
            array_replace(
283
                $dependsNormalized,
284
                array(PatchDefinition::BUNDLE_TARGET => false, '' => false)
285
            )
286
        );
287
    }
288
289
    private function extractValueList(array $data, $name, $default = array())
290
    {
291
        return isset($data[$name]) ? array_filter($data[$name]) : $default;
292
    }
293
294
    private function extractSingleValue(array $data, $name, $default = null)
295
    {
296
        return isset($data[$name]) ? reset($data[$name]) : $default;
297
    }
298
299
    private function applyAliases(array $data, array $aliases)
300
    {
301
        foreach ($aliases as $target => $origins) {
302
            if (isset($data[$target])) {
303
                continue;
304
            }
305
306
            foreach ($origins as $origin) {
307
                if (!isset($data[$origin])) {
308
                    continue;
309
                }
310
311
                $data[$target] = $data[$origin];
312
            }
313
        }
314
315
        return $data;
316
    }
317
}
318