Completed
Pull Request — master (#2)
by ARCANEDEV
12:25
created

Package::getRequires()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 3
nc 2
nop 0
crap 2
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->mergeConflicts($root);
113 80
        $this->mergeReplaces($root);
114 80
        $this->mergeProvides($root);
115 80
        $this->mergeSuggests($root);
116
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 conflicting packages into a RootPackage.
337
     *
338
     * @param  \Composer\Package\RootPackageInterface  $root
339
     */
340 80
    protected function mergeConflicts(RootPackageInterface $root)
341
    {
342 80
        $conflicts = $this->package->getConflicts();
343
344 80
        if (empty($conflicts)) return;
345
346 10
        $unwrapped = self::unwrapIfNeeded($root, 'setConflicts');
347
348
        // @codeCoverageIgnoreStart
349
        if ($root !== $unwrapped) {
350
            $this->logger->warning(
351
                'This Composer version does not support ' .
352
                "'conflicts' merging for aliased packages."
353
            );
354
        }
355
        // @codeCoverageIgnoreEnd
356
357 10
        $unwrapped->setConflicts(array_merge(
358 10
            $root->getConflicts(),
359 10
            $this->replaceSelfVersionDependencies('conflict', $conflicts, $root)
360 8
        ));
361 10
    }
362
363
    /**
364
     * Merge replaced packages into a RootPackage.
365
     *
366
     * @param  \Composer\Package\RootPackageInterface  $root
367
     */
368 80
    protected function mergeReplaces(RootPackageInterface $root)
369
    {
370 80
        $replaces = $this->package->getReplaces();
371
372 80
        if (empty($replaces)) return;
373
374 15
        $unwrapped = self::unwrapIfNeeded($root, 'setReplaces');
375
376
        // @codeCoverageIgnoreStart
377
        if ($root !== $unwrapped) {
378
            $this->logger->warning(
379
                'This Composer version does not support ' .
380
                "'replaces' merging for aliased packages."
381
            );
382
        }
383
        // @codeCoverageIgnoreEnd
384
385 15
        $unwrapped->setReplaces(array_merge(
386 15
            $root->getReplaces(),
387 15
            $this->replaceSelfVersionDependencies('replace', $replaces, $root)
388 12
        ));
389 15
    }
390
391
    /**
392
     * Merge provided virtual packages into a RootPackage.
393
     *
394
     * @param  \Composer\Package\RootPackageInterface  $root
395
     */
396 80
    protected function mergeProvides(RootPackageInterface $root)
397
    {
398 80
        $provides  = $this->package->getProvides();
399
400 80
        if (empty($provides)) return;
401
402 10
        $unwrapped = self::unwrapIfNeeded($root, 'setProvides');
403
404
        // @codeCoverageIgnoreStart
405
        if ($root !== $unwrapped) {
406
            $this->logger->warning(
407
                'This Composer version does not support ' .
408
                "'provides' merging for aliased packages."
409
            );
410
        }
411
        // @codeCoverageIgnoreEnd
412
413 10
        $unwrapped->setProvides(array_merge(
414 10
            $root->getProvides(),
415 10
            $this->replaceSelfVersionDependencies('provide', $provides, $root)
416 8
        ));
417 10
    }
418
419
    /**
420
     * Merge suggested packages into a RootPackage.
421
     *
422
     * @param  \Composer\Package\RootPackageInterface  $root
423
     */
424 80
    private function mergeSuggests(RootPackageInterface $root)
425
    {
426 80
        $suggests = $this->package->getSuggests();
427
428 80
        if (empty($suggests)) return;
429
430 10
        self::unwrapIfNeeded($root, 'setSuggests')
431 10
            ->setSuggests(array_merge($root->getSuggests(), $suggests));
432 10
    }
433
434
    /**
435
     * Merge extra config into a RootPackage.
436
     *
437
     * @param  \Composer\Package\RootPackageInterface    $root
438
     * @param  \Arcanedev\Composer\Entities\PluginState  $state
439
     */
440 80
    private function mergeExtra(RootPackageInterface $root, PluginState $state)
441
    {
442 80
        $extra     = $this->package->getExtra();
443 80
        $unwrapped = self::unwrapIfNeeded($root, 'setExtra');
444
445 80
        unset($extra['merge-plugin']);
446
447 80
        if ( ! $state->shouldMergeExtra() || empty($extra)) {
448 65
            return;
449
        }
450
451 15
        $mergedExtra = $this->getExtra($unwrapped, $state, $extra);
452
453 15
        $unwrapped->setExtra($mergedExtra);
454 15
    }
455
456
    /**
457
     * Get extra config.
458
     *
459
     * @param  \Composer\Package\RootPackageInterface    $root
460
     * @param  \Arcanedev\Composer\Entities\PluginState  $state
461
     * @param  array                                     $extra
462
     *
463
     * @return array
464
     */
465 15
    private function getExtra(
466
        RootPackageInterface $root, PluginState $state, $extra
467
    ) {
468 15
        $rootExtra   = $root->getExtra();
469 15
        $mergedExtra = array_merge($rootExtra, $extra);
470
471 15
        if ($state->replaceDuplicateLinks()) {
472 5
            return $mergedExtra;
473
        }
474
475 10
        foreach (array_intersect(array_keys($extra), array_keys($rootExtra)) as $key) {
476 5
            $this->logger->info(
477 5
                "Ignoring duplicate <comment>{$key}</comment> in <comment>{$this->path}</comment> extra config."
478 4
            );
479 8
        }
480
481 10
        return array_merge($extra, $rootExtra);
482
    }
483
484
    /**
485
     * Get a full featured Package from a RootPackageInterface.
486
     *
487
     * @param  \Composer\Package\RootPackageInterface|\Composer\Package\RootPackage  $root
488
     * @param  string                                                                $method
489
     *
490
     * @return \Composer\Package\RootPackageInterface|\Composer\Package\RootPackage
491
     */
492 80
    private static function unwrapIfNeeded(
493
        RootPackageInterface $root, $method = 'setExtra'
494
    ) {
495 80
        return ($root instanceof RootAliasPackage && ! method_exists($root, $method))
496 64
            ? $root->getAliasOf()
497 80
            : $root;
498
    }
499
}
500