Completed
Push — master ( 96e76d...afb99c )
by ARCANEDEV
06:33
created

Package::mergePackageLinks()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 26
ccs 12
cts 12
cp 1
rs 8.8571
cc 3
eloc 14
nc 3
nop 2
crap 3
1
<?php namespace Arcanedev\Composer\Entities;
2
3
use Arcanedev\Composer\Utilities\Logger;
4
use Arcanedev\Composer\Utilities\Util;
5
use Composer\Composer;
6
use Composer\Package\BasePackage;
7
use Composer\Package\Link;
8
use Composer\Package\RootAliasPackage;
9
use Composer\Package\RootPackageInterface;
10
use Composer\Package\Version\VersionParser;
11
use Composer\Repository\RepositoryManager;
12
13
/**
14
 * Class     Package
15
 *
16
 * @package  Arcanedev\Composer\Entities
17
 * @author   ARCANEDEV <[email protected]>
18
 */
19
class Package
20
{
21
    /* ------------------------------------------------------------------------------------------------
22
     |  Properties
23
     | ------------------------------------------------------------------------------------------------
24
     */
25
    /** @var \Composer\Composer $composer */
26
    protected $composer;
27
28
    /** @var \Arcanedev\Composer\Utilities\Logger $logger */
29
    protected $logger;
30
31
    /** @var string $path */
32
    protected $path;
33
34
    /** @var array $json */
35
    protected $json;
36
37
    /** @var \Composer\Package\CompletePackage $package */
38
    protected $package;
39
40
    /* ------------------------------------------------------------------------------------------------
41
     |  Constructor
42
     | ------------------------------------------------------------------------------------------------
43
     */
44
    /**
45
     * Make a Package instance.
46
     *
47
     * @param  string                                $path
48
     * @param  \Composer\Composer                    $composer
49
     * @param  \Arcanedev\Composer\Utilities\Logger  $logger
50
     */
51 80
    public function __construct($path, Composer $composer, Logger $logger)
52
    {
53 80
        $this->path     = $path;
54 80
        $this->composer = $composer;
55 80
        $this->logger   = $logger;
56 80
        $this->json     = PackageJson::read($path);
57 80
        $this->package  = PackageJson::convert($this->json);
58 80
    }
59
60
    /* ------------------------------------------------------------------------------------------------
61
     |  Getters & Setters
62
     | ------------------------------------------------------------------------------------------------
63
     */
64
    /**
65
     * Get list of additional packages to require if precessing recursively.
66
     *
67
     * @return array
68
     */
69 75
    public function getRequires()
70
    {
71 75
        return isset($this->json['extra']['merge-plugin']['require'])
72 60
            ? $this->json['extra']['merge-plugin']['require']
73 75
            : [];
74
    }
75
76
    /**
77
     * Get list of additional packages to include if precessing recursively.
78
     *
79
     * @return array
80
     */
81 75
    public function getIncludes()
82
    {
83 75
        if (isset($this->json['extra']['merge-plugin']['include'])) {
84 15
            return $this->json['extra']['merge-plugin']['include'];
85
        }
86
87 65
        return [];
88
    }
89
90
    /* ------------------------------------------------------------------------------------------------
91
     |  Main Functions
92
     | ------------------------------------------------------------------------------------------------
93
     */
94
    /**
95
     * Merge this package into a RootPackage.
96
     *
97
     * @param  \Composer\Package\RootPackageInterface    $root
98
     * @param  \Arcanedev\Composer\Entities\PluginState  $state
99
     */
100 80
    public function mergeInto(RootPackageInterface $root, PluginState $state)
101
    {
102 80
        $this->addRepositories($root);
103
104 80
        $this->mergeRequires($root, $state);
105 80
        $this->mergeAutoload($root);
106
107 80
        if ($state->isDevMode()) {
108 80
            $this->mergeDevRequires($root, $state);
109 80
            $this->mergeDevAutoload($root);
110 64
        }
111
112 80
        $this->mergePackageLinks('conflict', $root);
113 80
        $this->mergePackageLinks('replace',  $root);
114 80
        $this->mergePackageLinks('provide',  $root);
115
116 80
        $this->mergeSuggests($root);
117 80
        $this->mergeExtra($root, $state);
118 80
    }
119
120
    /**
121
     * Add a collection of repositories described by the given configuration
122
     * to the given package and the global repository manager.
123
     *
124
     * @param  \Composer\Package\RootPackageInterface  $root
125
     */
126 80
    private function addRepositories(RootPackageInterface $root)
127
    {
128 80
        if ( ! isset($this->json['repositories'])) return;
129
130 10
        $repoManager  = $this->composer->getRepositoryManager();
131 10
        $repositories = [];
132
133 10
        foreach ($this->json['repositories'] as $repoJson) {
134 10
            $this->addRepository($repoManager, $repositories, $repoJson);
135 8
        }
136
137 10
        self::unwrapIfNeeded($root, 'setRepositories')
138 10
            ->setRepositories(array_merge($repositories, $root->getRepositories()));
139 10
    }
140
141
    /**
142
     * Add a repository to collection of repositories.
143
     *
144
     * @param  \Composer\Repository\RepositoryManager  $repoManager
145
     * @param  array                                   $repositories
146
     * @param  array                                   $repoJson
147
     */
148 10
    private function addRepository(
149
        RepositoryManager $repoManager,
150
        array &$repositories,
151
        $repoJson
152
    ) {
153 10
        if ( ! isset($repoJson['type'])) return;
154
155 10
        $this->logger->info("Adding {$repoJson['type']} repository");
156
157 10
        $repository = $repoManager->createRepository(
158 10
            $repoJson['type'], $repoJson
159 8
        );
160
161 10
        $repoManager->addRepository($repository);
162 10
        $repositories[] = $repository;
163 10
    }
164
165
    /**
166
     * Merge require into a RootPackage.
167
     *
168
     * @param  \Composer\Package\RootPackageInterface    $root
169
     * @param  \Arcanedev\Composer\Entities\PluginState  $state
170
     */
171 80
    private function mergeRequires(RootPackageInterface $root, PluginState $state)
172
    {
173 80
        $requires = $this->package->getRequires();
174
175 80
        if (empty($requires)) return;
176
177 55
        $this->mergeStabilityFlags($root, $requires);
178
179 55
        $duplicateLinks = [];
180 55
        $requires       = $this->replaceSelfVersionDependencies(
181 55
            'require', $requires, $root
182 44
        );
183
184 55
        $root->setRequires($this->mergeLinks(
185 55
            $root->getRequires(),
186 44
            $requires,
187 55
            $state->replaceDuplicateLinks(),
188
            $duplicateLinks
189 44
        ));
190
191 55
        $state->addDuplicateLinks('require', $duplicateLinks);
192 55
    }
193
194
    /**
195
     * Merge require-dev into RootPackage.
196
     *
197
     * @param  \Composer\Package\RootPackageInterface    $root
198
     * @param  \Arcanedev\Composer\Entities\PluginState  $state
199
     */
200 80
    private function mergeDevRequires(RootPackageInterface $root, PluginState $state)
201
    {
202 80
        $requires = $this->package->getDevRequires();
203
204 80
        if (empty($requires)) return;
205
206 10
        $this->mergeStabilityFlags($root, $requires);
207
208 10
        $duplicateLinks = [];
209 10
        $requires       = $this->replaceSelfVersionDependencies(
210 10
            'require-dev', $requires, $root
211 8
        );
212
213 10
        $root->setDevRequires($this->mergeLinks(
214 10
            $root->getDevRequires(),
215 8
            $requires,
216 10
            $state->replaceDuplicateLinks(),
217
            $duplicateLinks
218 8
        ));
219
220 10
        $state->addDuplicateLinks('require-dev', $duplicateLinks);
221 10
    }
222
223
    /**
224
     * Update Links with a 'self.version' constraint with the root package's version.
225
     *
226
     * @param  string                                  $type
227
     * @param  array                                   $links
228
     * @param  \Composer\Package\RootPackageInterface  $root
229
     *
230
     * @return array
231
     */
232 60
    protected function replaceSelfVersionDependencies(
233
        $type, array $links, RootPackageInterface $root
234
    ) {
235 60
        $linkType      = BasePackage::$supportedLinkTypes[$type];
236 60
        $version       = $root->getVersion();
237 60
        $prettyVersion = $root->getPrettyVersion();
238 60
        $vp            = new VersionParser;
239
240 60
        return array_map(function (Link $link) use ($linkType, $version, $prettyVersion, $vp) {
241 60
            if ('self.version' === $link->getPrettyConstraint()) {
242 5
                return new Link(
243 5
                    $link->getSource(),
244 5
                    $link->getTarget(),
245 5
                    $vp->parseConstraints($version),
246 5
                    $linkType['description'],
247
                    $prettyVersion
248 4
                );
249
            }
250
251 60
            return $link;
252 60
        }, $links);
253
    }
254
255
    /**
256
     * Merge two collections of package links and collect duplicates for subsequent processing.
257
     *
258
     * @param  array  $origin          Primary collection
259
     * @param  array  $merge           Additional collection
260
     * @param  bool   $replace         Replace existing links ?
261
     * @param  array  $duplicateLinks  Duplicate storage
262
     *
263
     * @return array                   Merged collection
264
     */
265 55
    private function mergeLinks(array $origin, array $merge, $replace, array &$duplicateLinks)
266
    {
267 55
        foreach ($merge as $name => $link) {
268 55
            if ( ! isset($origin[$name]) || $replace) {
269 55
                $this->logger->info("Merging <comment>{$name}</comment>");
270 55
                $origin[$name] = $link;
271 44
            }
272
            else {
273
                // Defer to solver.
274 10
                $this->logger->info("Deferring duplicate <comment>{$name}</comment>");
275 19
                $duplicateLinks[] = $link;
276
            }
277 44
        }
278
279 55
        return $origin;
280
    }
281
282
    /**
283
     * Merge autoload into a RootPackage.
284
     *
285
     * @param  \Composer\Package\RootPackageInterface  $root
286
     */
287 80
    private function mergeAutoload(RootPackageInterface $root)
288
    {
289 80
        $autoload = $this->package->getAutoload();
290
291 80
        if (empty($autoload)) return;
292
293 10
        self::unwrapIfNeeded($root, 'setAutoload')
294 10
            ->setAutoload(array_merge_recursive(
295 10
                $root->getAutoload(), Util::fixRelativePaths($this->path, $autoload)
296 8
            ));
297 10
    }
298
299
    /**
300
     * Merge autoload-dev into a RootPackage.
301
     *
302
     * @param  \Composer\Package\RootPackageInterface  $root
303
     */
304 80
    private function mergeDevAutoload(RootPackageInterface $root)
305
    {
306 80
        $autoload = $this->package->getDevAutoload();
307
308 80
        if (empty($autoload)) return;
309
310 10
        self::unwrapIfNeeded($root, 'setDevAutoload')
311 10
            ->setDevAutoload(array_merge_recursive(
312 10
                $root->getDevAutoload(), Util::fixRelativePaths($this->path, $autoload)
313 8
            ));
314 10
    }
315
316
    /**
317
     * Extract and merge stability flags from the given collection of
318
     * requires and merge them into a RootPackage.
319
     *
320
     * @param  \Composer\Package\RootPackageInterface  $root
321
     * @param  array                                   $requires
322
     */
323 55
    private function mergeStabilityFlags(RootPackageInterface $root, array $requires)
324
    {
325 55
        $flags = StabilityFlags::extract(
326 55
            $root->getStabilityFlags(),
327 55
            $root->getMinimumStability(),
328
            $requires
329 44
        );
330
331 55
        self::unwrapIfNeeded($root, 'setStabilityFlags')
332 55
            ->setStabilityFlags($flags);
333 55
    }
334
335
    /**
336
     * Merge package links of the given type into a RootPackageInterface
337
     *
338
     * @param  string                                  $type  'conflict', 'replace' or 'provide'
339
     * @param  \Composer\Package\RootPackageInterface  $root
340
     */
341 80
    protected function mergePackageLinks($type, RootPackageInterface $root)
342
    {
343 80
        $linkType = BasePackage::$supportedLinkTypes[$type];
344 80
        $getter   = 'get' . ucfirst($linkType['method']);
345 80
        $setter   = 'set' . ucfirst($linkType['method']);
346
347 80
        $links = $this->package->{$getter}();
348
349 80
        if (empty($links)) return;
350
351 15
        $unwrapped = self::unwrapIfNeeded($root, $setter);
352
353
        // @codeCoverageIgnoreStart
354
        if ($root !== $unwrapped) {
355
            $this->logger->warning(
356
                'This Composer version does not support ' .
357
                "'{$type}' merging for aliased packages."
358
            );
359
        }
360
        // @codeCoverageIgnoreEnd
361
362 15
        $unwrapped->{$setter}(array_merge(
363 15
            $root->{$getter}(),
364 15
            $this->replaceSelfVersionDependencies($type, $links, $root)
365 12
        ));
366 15
    }
367
368
    /**
369
     * Merge suggested packages into a RootPackage.
370
     *
371
     * @param  \Composer\Package\RootPackageInterface  $root
372
     */
373 80
    private function mergeSuggests(RootPackageInterface $root)
374
    {
375 80
        $suggests = $this->package->getSuggests();
376
377 80
        if (empty($suggests)) return;
378
379 10
        self::unwrapIfNeeded($root, 'setSuggests')
380 10
            ->setSuggests(array_merge($root->getSuggests(), $suggests));
381 10
    }
382
383
    /**
384
     * Merge extra config into a RootPackage.
385
     *
386
     * @param  \Composer\Package\RootPackageInterface    $root
387
     * @param  \Arcanedev\Composer\Entities\PluginState  $state
388
     */
389 80
    private function mergeExtra(RootPackageInterface $root, PluginState $state)
390
    {
391 80
        $extra     = $this->package->getExtra();
392 80
        $unwrapped = self::unwrapIfNeeded($root, 'setExtra');
393
394 80
        unset($extra['merge-plugin']);
395
396 80
        if ( ! $state->shouldMergeExtra() || empty($extra)) {
397 65
            return;
398
        }
399
400 15
        $mergedExtra = $this->getExtra($unwrapped, $state, $extra);
401
402 15
        $unwrapped->setExtra($mergedExtra);
403 15
    }
404
405
    /**
406
     * Get extra config.
407
     *
408
     * @param  \Composer\Package\RootPackageInterface    $root
409
     * @param  \Arcanedev\Composer\Entities\PluginState  $state
410
     * @param  array                                     $extra
411
     *
412
     * @return array
413
     */
414 15
    private function getExtra(
415
        RootPackageInterface $root, PluginState $state, $extra
416
    ) {
417 15
        $rootExtra   = $root->getExtra();
418 15
        $mergedExtra = array_merge($rootExtra, $extra);
419
420 15
        if ($state->replaceDuplicateLinks()) {
421 5
            return $mergedExtra;
422
        }
423
424 10
        foreach (array_intersect(array_keys($extra), array_keys($rootExtra)) as $key) {
425 5
            $this->logger->info(
426 5
                "Ignoring duplicate <comment>{$key}</comment> in <comment>{$this->path}</comment> extra config."
427 4
            );
428 8
        }
429
430 10
        return array_merge($extra, $rootExtra);
431
    }
432
433
    /**
434
     * Get a full featured Package from a RootPackageInterface.
435
     *
436
     * @param  \Composer\Package\RootPackageInterface|\Composer\Package\RootPackage  $root
437
     * @param  string                                                                $method
438
     *
439
     * @return \Composer\Package\RootPackageInterface|\Composer\Package\RootPackage
440
     */
441 80
    private static function unwrapIfNeeded(
442
        RootPackageInterface $root, $method = 'setExtra'
443
    ) {
444 80
        return ($root instanceof RootAliasPackage && ! method_exists($root, $method))
445 64
            ? $root->getAliasOf()
446 80
            : $root;
447
    }
448
}
449