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

Package::extractReferences()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

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