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