PatchesSearch   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 310
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 2
Metric Value
eloc 144
c 4
b 1
f 2
dl 0
loc 310
rs 9.44
wmc 37

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstallPath() 0 7 2
A load() 0 19 3
A createPatchDefinitions() 0 30 4
A resolveBaseData() 0 19 3
A normalizeDependencies() 0 23 2
A applyAliases() 0 17 5
A extractValueList() 0 3 2
A __construct() 0 25 1
A extractSingleValue() 0 3 2
B resolveBaseInfo() 0 36 6
B createDefinitionItem() 0 47 7
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\RootPackageInterface) {
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
        list($package, $version, $depends) = $this->resolveBaseData($data);
212
213
        if ($package) {
214
            $target = $package;
215
        }
216
217
        if (!$target && $depends) {
218
            $target = $depends;
219
        }
220
221
        if (!$depends && $target) {
222
            $depends = $target;
223
        }
224
225
        $dependsList = array_merge(
226
            array(sprintf('%s:%s', $package, $version)),
227
            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

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