Completed
Pull Request — master (#3)
by ARCANEDEV
06:00
created

Package::replaceSelfVersionDependencies()   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 3

Importance

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