Completed
Pull Request — master (#6)
by ARCANEDEV
06:36
created

Package::mergeDev()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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