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

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