Passed
Push — master ( 65f41a...2c251c )
by Allan
02:57 queued 10s
created

PatchesSearch::normalizeDependencies()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 13
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 20
rs 9.8333
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 = array();
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
        $basePathLength = strlen($basePath);
104
105
        $results = array();
106
107
        foreach ($source as $item) {
108
            $paths = $this->fileSystemUtils->collectPathsRecursively(
109
                $basePath . DIRECTORY_SEPARATOR . $item,
110
                PluginConfig::PATCH_FILE_REGEX_MATCHER
111
            );
112
113
            $groups = array();
114
115
            foreach ($paths as $path) {
116
                $contents = $this->patchFileLoader->loadWithNormalizedLineEndings($path);
117
118
                $definition = $this->createDefinitionItem($contents, array(
119
                    PatchDefinition::PATH => $path,
120
                    PatchDefinition::SOURCE => trim(
121
                        substr($path, $basePathLength),
122
                        DIRECTORY_SEPARATOR
123
                    )
124
                ));
125
126
                if (!isset($definition[PatchDefinition::TARGET])) {
127
                    continue;
128
                }
129
130
                $target = $definition[PatchDefinition::TARGET];
131
132
                if (!isset($groups[$target])) {
133
                    $groups[$target] = array();
134
                }
135
136
                $groups[$target][] = $definition;
137
            }
138
139
            $results[] = $groups;
140
        }
141
142
        return $results;
143
    }
144
145
    private function getInstallPath(\Composer\Package\PackageInterface $package)
146
    {
147
        if ($package instanceof \Composer\Package\RootPackage) {
148
            return getcwd();
149
        }
150
151
        return $this->installationManager->getInstallPath($package);
152
    }
153
154
    private function createDefinitionItem($contents, array $values = array())
155
    {
156
        $header = $this->fileAnalyser->getHeader($contents);
157
158
        $data = $this->applyAliases(
159
            $this->patchHeaderParser->parseContents($header),
160
            $this->tagAliases
161
        );
162
163
        list($target, $depends, $flags) = $this->resolveBaseInfo($data);
164
        
165
        if (array_intersect_key($flags, array_flip($this->bundledModeTypes))) {
166
            $target = PatchDefinition::BUNDLE_TARGET;
167
        }
168
        
169
        if (!$target) {
170
            return array();
171
        }
172
173
        if (array_intersect_key($flags, array_flip($this->localTypes))) {
174
            $values[PatchDefinition::LOCAL] = true;
175
        }
176
        
177
        if (!$this->devMode && array_intersect_key($flags, array_flip($this->devModeTypes))) {
178
            $data[PatchDefinition::SKIP] = true;
179
        }
180
        
181
        return array_replace(array(
182
            PatchDefinition::LABEL => implode(
183
                PHP_EOL,
184
                isset($data[PatchDefinition::LABEL]) ? $data[PatchDefinition::LABEL] : array('')
185
            ),
186
            PatchDefinition::TARGET => $target,
187
            PatchDefinition::CWD => $this->extractSingleValue($data, PatchDefinition::CWD),
188
            PatchDefinition::TARGETS => $this->extractValueList(
189
                $data,
190
                PatchDefinition::TARGETS,
191
                array($target)
192
            ),
193
            PatchDefinition::DEPENDS => $depends,
194
            PatchDefinition::SKIP => isset($data[PatchDefinition::SKIP]),
195
            PatchDefinition::AFTER => $this->extractValueList($data, PatchDefinition::AFTER),
196
            PatchDefinition::ISSUE => $this->extractSingleValue($data, PatchDefinition::ISSUE),
197
            PatchDefinition::LINK => $this->extractSingleValue($data, PatchDefinition::LINK),
198
            PatchDefinition::LEVEL => $this->extractSingleValue($data, PatchDefinition::LEVEL),
199
            PatchDefinition::CATEGORY => $this->extractSingleValue($data, PatchDefinition::CATEGORY)
200
        ), $values);
201
    }
202
    
203
    private function resolveBaseInfo(array $data)
204
    {
205
        $target = false;
206
207
        $package = $this->extractSingleValue($data, PatchDefinition::PACKAGE);
208
        $depends = $this->extractSingleValue($data, PatchDefinition::DEPENDS);
209
        $version = $this->extractSingleValue($data, PatchDefinition::VERSION, '>=0.0.0');
210
211
        if (strpos($version, ':') !== false) {
212
            $valueParts = explode(':', $version);
213
214
            $depends = trim(array_shift($valueParts));
215
            $version = trim(implode(':', $valueParts));
216
        }
217
        
218
        if (strpos($package, ':') !== false) {
219
            $valueParts = explode(':', $package);
220
221
            $package = trim(array_shift($valueParts));
222
            $version = trim(implode(':', $valueParts));
223
        }
224
225
        if (!$target && $package) {
0 ignored issues
show
introduced by
The condition $target is always false.
Loading history...
226
            $target = $package;
227
        }
228
229
        if (!$target && $depends) {
230
            $target = $depends;
231
        }
232
233
        if (!$depends && $target) {
234
            $depends = $target;
235
        }
236
237
        $dependsList = array_merge(
238
            array($package . ':' . $version),
239
            array($depends . ':' . $version),
240
            $this->extractValueList($data, PatchDefinition::DEPENDS)
241
        );
242
        
243
        $patchTypeFlags = array_fill_keys(
244
            explode(
245
                PatchDefinition::TYPE_SEPARATOR,
246
                $this->extractSingleValue($data, PatchDefinition::TYPE)
247
            ),
248
            true
249
        );
250
        
251
        return array(
252
            $target,
253
            $this->normalizeDependencies($dependsList),
254
            $patchTypeFlags
255
        );
256
    }
257
    
258
    private function normalizeDependencies($dependsList)
259
    {
260
        $dependsNormalized = array_map(function ($item) {
261
            $valueParts = explode(':', $item);
262
263
            return array(
264
                trim(array_shift($valueParts)) => trim(array_shift($valueParts)) ?: '>=0.0.0'
265
            );
266
        }, array_unique($dependsList));
267
268
        $dependsNormalized = array_reduce(
269
            $dependsNormalized,
270
            'array_replace',
271
            array()
272
        );
273
274
        return array_filter(
275
            array_replace(
276
                $dependsNormalized,
277
                array(PatchDefinition::BUNDLE_TARGET => false, '' => false)
278
            )
279
        );
280
    }
281
282
    private function extractValueList(array $data, $name, $default = array())
283
    {
284
        return isset($data[$name]) ? array_filter($data[$name]) : $default;
285
    }
286
287
    private function extractSingleValue(array $data, $name, $default = null)
288
    {
289
        return isset($data[$name]) ? reset($data[$name]) : $default;
290
    }
291
292
    private function applyAliases(array $data, array $aliases)
293
    {
294
        foreach ($aliases as $target => $origins) {
295
            if (isset($data[$target])) {
296
                continue;
297
            }
298
299
            foreach ($origins as $origin) {
300
                if (!isset($data[$origin])) {
301
                    continue;
302
                }
303
304
                $data[$target] = $data[$origin];
305
            }
306
        }
307
308
        return $data;
309
    }
310
}
311