Completed
Push — master ( 941968...69b875 )
by ARCANEDEV
9s
created

Package::mergeReferences()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

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