Completed
Push — master ( 516969...f657d9 )
by ARCANEDEV
8s
created

Package::mergeExtraArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
522 10
                $prettyVersion = $package->getPrettyConstraint();
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $prettyVersion, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
523 8
            }
524
525 15
            return new Link(
526 15
                $link->getSource(),
527 15
                $link->getTarget(),
528 15
                $vp->parseConstraints($version),
529 15
                $linkType['description'],
530
                $prettyVersion
531 12
            );
532 95
        }, $links);
533
    }
534
535
    /**
536
     * Get a full featured Package from a RootPackageInterface.
537
     *
538
     * @param  \Composer\Package\RootPackageInterface|\Composer\Package\RootPackage  $root
539
     * @param  string                                                                $method
540
     *
541
     * @return \Composer\Package\RootPackageInterface|\Composer\Package\RootPackage
542
     */
543 135
    private static function unwrapIfNeeded(
544
        RootPackageInterface $root, $method = 'setExtra'
545
    ) {
546 135
        return ($root instanceof RootAliasPackage && ! method_exists($root, $method))
547 110
            ? $root->getAliasOf()
548 135
            : $root;
549
    }
550
}
551