Completed
Pull Request — master (#98)
by
unknown
05:02
created

ExtraPackage   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 466
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 14

Test Coverage

Coverage 96.62%

Importance

Changes 18
Bugs 1 Features 5
Metric Value
wmc 44
c 18
b 1
f 5
lcom 2
cbo 14
dl 0
loc 466
ccs 200
cts 207
cp 0.9662
rs 8.3396

17 Methods

Rating   Name   Duplication   Size   Complexity  
A loadPackage() 0 14 2
A __construct() 0 9 1
A getIncludes() 0 5 2
A getRequires() 0 5 2
A readPackageJson() 0 18 4
A mergeInto() 0 22 3
B addRepositories() 0 27 4
B mergeRequires() 0 29 2
B mergeOrDefer() 0 22 4
A mergeAutoload() 0 16 2
A fixRelativePaths() 0 13 2
A mergeStabilityFlags() 0 13 1
A mergePackageLinks() 0 21 3
A mergeSuggests() 0 11 2
B mergeExtra() 0 31 5
B replaceSelfVersionDependencies() 0 26 2
A unwrapIfNeeded() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like ExtraPackage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExtraPackage, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of the Composer Merge plugin.
4
 *
5
 * Copyright (C) 2015 Bryan Davis, Wikimedia Foundation, and contributors
6
 *
7
 * This software may be modified and distributed under the terms of the MIT
8
 * license. See the LICENSE file for details.
9
 */
10
11
namespace Wikimedia\Composer\Merge;
12
13
use Wikimedia\Composer\Logger;
14
15
use Composer\Composer;
16
use Composer\Json\JsonFile;
17
use Composer\Package\BasePackage;
18
use Composer\Package\CompletePackage;
19
use Composer\Package\Link;
20
use Composer\Package\Loader\ArrayLoader;
21
use Composer\Package\RootAliasPackage;
22
use Composer\Package\RootPackage;
23
use Composer\Package\RootPackageInterface;
24
use Composer\Package\Version\VersionParser;
25
use Composer\IO\IOInterface;
26
use Composer\Util\RemoteFilesystem;
27
use UnexpectedValueException;
28
29
/**
30
 * Processing for a composer.json file that will be merged into
31
 * a RootPackageInterface
32
 *
33
 * @author Bryan Davis <[email protected]>
34
 */
35
class ExtraPackage
36
{
37
38
    /**
39
     * @var Composer $composer
40
     */
41
    protected $composer;
42
43
    /**
44
     * @var Logger $logger
45
     */
46
    protected $logger;
47
48
    /**
49
     * @var IOInterface $io
50
     */
51
    protected $io;
52
53
    /**
54
     * @var string $path
55
     */
56
    protected $path;
57
58
    /**
59
     * @var array $json
60
     */
61
    protected $json;
62
63
    /**
64
     * @var CompletePackage $package
65
     */
66
    protected $package;
67
68
    /**
69
     * @param string $path Path to composer.json file
70
     * @param Composer $composer
71
     * @param Logger $logger
72
     */
73 84
    public function __construct($path, Composer $composer, Logger $logger, IOInterface $io)
74
    {
75 84
        $this->path = $path;
76 84
        $this->composer = $composer;
77 84
        $this->logger = $logger;
78 84
        $this->io = $io;
79 84
        $this->json = $this->readPackageJson($path);
80 84
        $this->package = $this->loadPackage($this->json);
0 ignored issues
show
Documentation introduced by
$this->json is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
81 84
    }
82
83
    /**
84
     * Get list of additional packages to include if precessing recursively.
85
     *
86
     * @return array
87
     */
88 79
    public function getIncludes()
89
    {
90 79
        return isset($this->json['extra']['merge-plugin']['include']) ?
91 79
            $this->json['extra']['merge-plugin']['include'] : array();
92
    }
93
94
    /**
95
     * Get list of additional packages to require if precessing recursively.
96
     *
97
     * @return array
98
     */
99 79
    public function getRequires()
100
    {
101 79
        return isset($this->json['extra']['merge-plugin']['require']) ?
102 79
            $this->json['extra']['merge-plugin']['require'] : array();
103
    }
104
105
    /**
106
     * Read the contents of a composer.json style file into an array.
107
     *
108
     * The package contents are fixed up to be usable to create a Package
109
     * object by providing dummy "name" and "version" values if they have not
110
     * been provided in the file. This is consistent with the default root
111
     * package loading behavior of Composer.
112
     *
113
     * @param string $path
114
     * @return array
115
     */
116 84
    protected function readPackageJson($path)
117
    {
118 84
        if(substr($path, 0, 4) === 'http'){
119 4
            $file = new JsonFile($path, new RemoteFilesystem($this->io));
120 4
        }else{
121 80
            $file = new JsonFile($path);
122
        }
123
        
124 84
        $json = $file->read();
125 84
        if (!isset($json['name'])) {
126 84
            $json['name'] = 'merge-plugin/' .
127 84
                strtr($path, DIRECTORY_SEPARATOR, '-');
128 84
        }
129 84
        if (!isset($json['version'])) {
130 84
            $json['version'] = '1.0.0';
131 84
        }
132 84
        return $json;
133
    }
134
135
    /**
136
     * @param string $json
137
     * @return CompletePackage
138
     */
139 84
    protected function loadPackage($json)
140
    {
141 84
        $loader = new ArrayLoader();
142 84
        $package = $loader->load($json);
0 ignored issues
show
Documentation introduced by
$json is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
143
        // @codeCoverageIgnoreStart
144
        if (!$package instanceof CompletePackage) {
145
            throw new UnexpectedValueException(
146
                'Expected instance of CompletePackage, got ' .
147
                get_class($package)
148
            );
149
        }
150
        // @codeCoverageIgnoreEnd
151 84
        return $package;
152
    }
153
154
    /**
155
     * Merge this package into a RootPackageInterface
156
     *
157
     * @param RootPackageInterface $root
158
     * @param PluginState $state
159
     */
160 84
    public function mergeInto(RootPackageInterface $root, PluginState $state)
161
    {
162 84
        $this->addRepositories($root);
163
164 84
        $this->mergeRequires('require', $root, $state);
165 84
        if ($state->isDevMode()) {
166 79
            $this->mergeRequires('require-dev', $root, $state);
167 79
        }
168
169 84
        $this->mergePackageLinks('conflict', $root);
170 84
        $this->mergePackageLinks('replace', $root);
171 84
        $this->mergePackageLinks('provide', $root);
172
173 84
        $this->mergeSuggests($root);
174
175 84
        $this->mergeAutoload('autoload', $root);
176 84
        if ($state->isDevMode()) {
177 79
            $this->mergeAutoload('devAutoload', $root);
178 79
        }
179
180 84
        $this->mergeExtra($root, $state);
181 84
    }
182
183
    /**
184
     * Add a collection of repositories described by the given configuration
185
     * to the given package and the global repository manager.
186
     *
187
     * @param RootPackageInterface $root
188
     */
189 84
    protected function addRepositories(RootPackageInterface $root)
190
    {
191 84
        if (!isset($this->json['repositories'])) {
192 79
            return;
193
        }
194 5
        $repoManager = $this->composer->getRepositoryManager();
195 5
        $newRepos = array();
196
197 5
        foreach ($this->json['repositories'] as $repoJson) {
198 5
            if (!isset($repoJson['type'])) {
199 5
                continue;
200
            }
201 5
            $this->logger->info("Adding {$repoJson['type']} repository");
202 5
            $repo = $repoManager->createRepository(
203 5
                $repoJson['type'],
204
                $repoJson
205 5
            );
206 5
            $repoManager->addRepository($repo);
207 5
            $newRepos[] = $repo;
208 5
        }
209
210 5
        $unwrapped = self::unwrapIfNeeded($root, 'setRepositories');
211 5
        $unwrapped->setRepositories(array_merge(
212 5
            $newRepos,
213 5
            $root->getRepositories()
214 5
        ));
215 5
    }
216
217
    /**
218
     * Merge require or require-dev into a RootPackageInterface
219
     *
220
     * @param string $type 'require' or 'require-dev'
221
     * @param RootPackageInterface $root
222
     * @param PluginState $state
223
     */
224 84
    protected function mergeRequires(
225
        $type,
226
        RootPackageInterface $root,
227
        PluginState $state
228
    ) {
229 84
        $linkType = BasePackage::$supportedLinkTypes[$type];
230 84
        $getter = 'get' . ucfirst($linkType['method']);
231 84
        $setter = 'set' . ucfirst($linkType['method']);
232
233 84
        $requires = $this->package->{$getter}();
234 84
        if (empty($requires)) {
235 74
            return;
236
        }
237
238 59
        $this->mergeStabilityFlags($root, $requires);
239
240 59
        $requires = $this->replaceSelfVersionDependencies(
241 59
            $type,
242 59
            $requires,
243
            $root
244 59
        );
245
246 59
        $root->{$setter}($this->mergeOrDefer(
247 59
            $type,
248 59
            $root->{$getter}(),
249 59
            $requires,
250
            $state
251 59
        ));
252 59
    }
253
254
    /**
255
     * Merge two collections of package links and collect duplicates for
256
     * subsequent processing.
257
     *
258
     * @param string $type 'require' or 'require-dev'
259
     * @param array $origin Primary collection
260
     * @param array $merge Additional collection
261
     * @param PluginState $state
262
     * @return array Merged collection
263
     */
264 59
    protected function mergeOrDefer(
265
        $type,
266
        array $origin,
267
        array $merge,
268
        $state
269
    ) {
270 59
        $dups = array();
271 59
        foreach ($merge as $name => $link) {
272 59
            if (!isset($origin[$name]) || $state->replaceDuplicateLinks()) {
273 59
                $this->logger->info("Merging <comment>{$name}</comment>");
274 59
                $origin[$name] = $link;
275 59
            } else {
276
                // Defer to solver.
277 10
                $this->logger->info(
278 10
                    "Deferring duplicate <comment>{$name}</comment>"
279 10
                );
280 10
                $dups[] = $link;
281
            }
282 59
        }
283 59
        $state->addDuplicateLinks($type, $dups);
284 59
        return $origin;
285
    }
286
287
    /**
288
     * Merge autoload or autoload-dev into a RootPackageInterface
289
     *
290
     * @param string $type 'autoload' or 'devAutoload'
291
     * @param RootPackageInterface $root
292
     */
293 84
    protected function mergeAutoload($type, RootPackageInterface $root)
294
    {
295 84
        $getter = 'get' . ucfirst($type);
296 84
        $setter = 'set' . ucfirst($type);
297
298 84
        $autoload = $this->package->{$getter}();
299 84
        if (empty($autoload)) {
300 84
            return;
301
        }
302
303 5
        $unwrapped = self::unwrapIfNeeded($root, $setter);
304 5
        $unwrapped->{$setter}(array_merge_recursive(
305 5
            $root->{$getter}(),
306 5
            $this->fixRelativePaths($autoload)
307 5
        ));
308 5
    }
309
310
    /**
311
     * Fix a collection of paths that are relative to this package to be
312
     * relative to the base package.
313
     *
314
     * @param array $paths
315
     * @return array
316
     */
317 5
    protected function fixRelativePaths(array $paths)
318
    {
319 5
        $base = dirname($this->path);
320 5
        $base = ($base === '.') ? '' : "{$base}/";
321
322 5
        array_walk_recursive(
323 5
            $paths,
324
            function (&$path) use ($base) {
325 5
                $path = "{$base}{$path}";
326 5
            }
327 5
        );
328 5
        return $paths;
329
    }
330
331
    /**
332
     * Extract and merge stability flags from the given collection of
333
     * requires and merge them into a RootPackageInterface
334
     *
335
     * @param RootPackageInterface $root
336
     * @param array $requires
337
     */
338 59
    protected function mergeStabilityFlags(
339
        RootPackageInterface $root,
340
        array $requires
341
    ) {
342 59
        $flags = $root->getStabilityFlags();
343 59
        $sf = new StabilityFlags($flags, $root->getMinimumStability());
344
345 59
        $unwrapped = self::unwrapIfNeeded($root, 'setStabilityFlags');
346 59
        $unwrapped->setStabilityFlags(array_merge(
347 59
            $flags,
348 59
            $sf->extractAll($requires)
349 59
        ));
350 59
    }
351
352
    /**
353
     * Merge package links of the given type  into a RootPackageInterface
354
     *
355
     * @param string $type 'conflict', 'replace' or 'provide'
356
     * @param RootPackageInterface $root
357
     */
358 84
    protected function mergePackageLinks($type, RootPackageInterface $root)
359
    {
360 84
        $linkType = BasePackage::$supportedLinkTypes[$type];
361 84
        $getter = 'get' . ucfirst($linkType['method']);
362 84
        $setter = 'set' . ucfirst($linkType['method']);
363
364 84
        $links = $this->package->{$getter}();
365 84
        if (!empty($links)) {
366 10
            $unwrapped = self::unwrapIfNeeded($root, $setter);
367 10
            if ($root !== $unwrapped) {
368
                $this->logger->warning(
369
                    'This Composer version does not support ' .
370
                    "'{$type}' merging for aliased packages."
371
                );
372
            }
373 10
            $unwrapped->{$setter}(array_merge(
374 10
                $root->{$getter}(),
375 10
                $this->replaceSelfVersionDependencies($type, $links, $root)
376 10
            ));
377 10
        }
378 84
    }
379
380
    /**
381
     * Merge suggested packages into a RootPackageInterface
382
     *
383
     * @param RootPackageInterface $root
384
     */
385 84
    protected function mergeSuggests(RootPackageInterface $root)
386
    {
387 84
        $suggests = $this->package->getSuggests();
388 84
        if (!empty($suggests)) {
389 5
            $unwrapped = self::unwrapIfNeeded($root, 'setSuggests');
390 5
            $unwrapped->setSuggests(array_merge(
391 5
                $root->getSuggests(),
392
                $suggests
393 5
            ));
394 5
        }
395 84
    }
396
397
    /**
398
     * Merge extra config into a RootPackageInterface
399
     *
400
     * @param RootPackageInterface $root
401
     * @param PluginState $state
402
     */
403 84
    public function mergeExtra(RootPackageInterface $root, PluginState $state)
404
    {
405 84
        $extra = $this->package->getExtra();
406 84
        unset($extra['merge-plugin']);
407 84
        if (!$state->shouldMergeExtra() || empty($extra)) {
408 69
            return;
409
        }
410
411 15
        $rootExtra = $root->getExtra();
412 15
        $unwrapped = self::unwrapIfNeeded($root, 'setExtra');
413
414 15
        if ($state->replaceDuplicateLinks()) {
415 5
            $unwrapped->setExtra(
416 5
                array_merge($rootExtra, $extra)
417 5
            );
418
419 5
        } else {
420 10
            foreach (array_intersect(
421 10
                array_keys($extra),
422 10
                array_keys($rootExtra)
423 10
            ) as $key) {
424 5
                $this->logger->info(
425 5
                    "Ignoring duplicate <comment>{$key}</comment> in ".
426 5
                    "<comment>{$this->path}</comment> extra config."
427 5
                );
428 10
            }
429 10
            $unwrapped->setExtra(
430 10
                array_merge($extra, $rootExtra)
431 10
            );
432
        }
433 15
    }
434
435
    /**
436
     * Update Links with a 'self.version' constraint with the root package's
437
     * version.
438
     *
439
     * @param string $type Link type
440
     * @param array $links
441
     * @param RootPackageInterface $root
442
     * @return array
443
     */
444 64
    protected function replaceSelfVersionDependencies(
445
        $type,
446
        array $links,
447
        RootPackageInterface $root
448
    ) {
449 64
        $linkType = BasePackage::$supportedLinkTypes[$type];
450 64
        $version = $root->getVersion();
451 64
        $prettyVersion = $root->getPrettyVersion();
452 64
        $vp = new VersionParser();
453
454 64
        return array_map(
455 64
            function ($link) use ($linkType, $version, $prettyVersion, $vp) {
456 64
                if ('self.version' === $link->getPrettyConstraint()) {
457 5
                    return new Link(
458 5
                        $link->getSource(),
459 5
                        $link->getTarget(),
460 5
                        $vp->parseConstraints($version),
461 5
                        $linkType['description'],
462
                        $prettyVersion
463 5
                    );
464
                }
465 64
                return $link;
466 64
            },
467
            $links
468 64
        );
469
    }
470
471
    /**
472
     * Get a full featured Package from a RootPackageInterface.
473
     *
474
     * In Composer versions before 599ad77 the RootPackageInterface only
475
     * defines a sub-set of operations needed by composer-merge-plugin and
476
     * RootAliasPackage only implemented those methods defined by the
477
     * interface. Most of the unimplemented methods in RootAliasPackage can be
478
     * worked around because the getter methods that are implemented proxy to
479
     * the aliased package which we can modify by unwrapping. The exception
480
     * being modifying the 'conflicts', 'provides' and 'replaces' collections.
481
     * We have no way to actually modify those collections unfortunately in
482
     * older versions of Composer.
483
     *
484
     * @param RootPackageInterface $root
485
     * @param string $method Method needed
486
     * @return RootPackageInterface|RootPackage
487
     */
488 84
    public static function unwrapIfNeeded(
489
        RootPackageInterface $root,
490
        $method = 'setExtra'
491
    ) {
492 84
        if ($root instanceof RootAliasPackage &&
493
            !method_exists($root, $method)
494 84
        ) {
495
            // Unwrap and return the aliased RootPackage.
496
            $root = $root->getAliasOf();
497
        }
498 84
        return $root;
499
    }
500
}
501
// vim:sw=4:ts=4:sts=4:et:
502