Completed
Push — master ( 3fd5c3...0ebcde )
by Andrii
13:53
created

Plugin   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 374
Duplicated Lines 0 %

Test Coverage

Coverage 8.51%

Importance

Changes 33
Bugs 1 Features 7
Metric Value
wmc 60
eloc 154
c 33
b 1
f 7
dl 0
loc 374
ccs 12
cts 141
cp 0.0851
rs 3.6

19 Methods

Rating   Name   Duplication   Size   Complexity  
A onPostAutoloadDump() 0 20 2
A scanPackages() 0 5 3
A reorderFiles() 0 7 3
A getSubscribedEvents() 0 5 1
A activate() 0 4 1
A initAutoload() 0 4 1
A readConfig() 0 9 2
B processPackage() 0 34 8
A addFile() 0 15 4
A setPackages() 0 3 1
A showDepsTree() 0 13 4
A addFiles() 0 9 4
A orderFiles() 0 14 4
A getAllFiles() 0 17 5
A iteratePackage() 0 25 4
A iterateDependencies() 0 6 5
A loadDotEnv() 0 5 3
A getPackages() 0 7 2
A findPackages() 0 16 3

How to fix   Complexity   

Complex Class

Complex classes like Plugin 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.

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 Plugin, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Composer plugin for config assembling
4
 *
5
 * @link      https://github.com/hiqdev/composer-config-plugin
6
 * @package   composer-config-plugin
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2016-2018, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\composer\config;
12
13
use Composer\Composer;
14
use Composer\EventDispatcher\EventSubscriberInterface;
15
use Composer\IO\IOInterface;
16
use Composer\Plugin\PluginInterface;
17
use Composer\Script\Event;
18
use Composer\Script\ScriptEvents;
19
use hiqdev\composer\config\exceptions\BadConfigurationException;
20
use hiqdev\composer\config\exceptions\FailedReadException;
21
use hiqdev\composer\config\readers\ReaderFactory;
22
23
/**
24
 * Plugin class.
25
 *
26
 * @author Andrii Vasyliev <[email protected]>
27
 */
28
class Plugin implements PluginInterface, EventSubscriberInterface
29
{
30
    /**
31
     * @var Package[] the array of active composer packages
32
     */
33
    protected $packages;
34
35
    private $alternatives = [];
36
37
    private $outputDir;
38
39
    private $rootPackage;
40
41
    /**
42
     * @var array config name => list of files
43
     */
44
    protected $files = [
45
        'dotenv'  => [],
46
        'defines' => [],
47
        'params'  => [],
48
    ];
49
50
    protected $colors = ['red', 'green', 'yellow', 'cyan', 'magenta', 'blue'];
51
52
    /**
53
     * @var array package name => configs as listed in `composer.json`
54
     */
55
    protected $originalFiles = [];
56
57
    /**
58
     * @var Builder
59
     */
60
    protected $builder;
61
62
    /**
63
     * @var Composer instance
64
     */
65
    protected $composer;
66
67
    /**
68
     * @var IOInterface
69
     */
70
    public $io;
71
72
    /**
73
     * Initializes the plugin object with the passed $composer and $io.
74
     * @param Composer $composer
75
     * @param IOInterface $io
76
     */
77
    public function activate(Composer $composer, IOInterface $io)
78
    {
79
        $this->composer = $composer;
80
        $this->io = $io;
81
    }
82
83
    /**
84
     * Returns list of events the plugin is subscribed to.
85
     * @return array list of events
86
     */
87
    public static function getSubscribedEvents()
88 2
    {
89
        return [
90 2
            ScriptEvents::POST_AUTOLOAD_DUMP => [
91 2
                ['onPostAutoloadDump', 0],
92 2
            ],
93
        ];
94
    }
95
96
    /**
97
     * This is the main function.
98 1
     * @param Event $event
99
     */
100
    public function onPostAutoloadDump(Event $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

100
    public function onPostAutoloadDump(/** @scrutinizer ignore-unused */ Event $event)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
101 1
    {
102
        $this->io->overwriteError('<info>Assembling config files</info>');
103
104
        $this->builder = new Builder();
105
106
        $this->initAutoload();
107
        $this->scanPackages();
108
        $this->reorderFiles();
109
        $this->showDepsTree();
110
111
        $this->builder->setOutputDir($this->outputDir);
112
        $this->builder->buildAllConfigs($this->files);
113
114
        $save = $this->files;
115
        foreach ($this->alternatives as $name => $files) {
116
            $this->files = $save;
117
            $builder = $this->builder->createAlternative($name);
118
            $this->addFiles($this->rootPackage, $files);
119
            $builder->buildAllConfigs($this->files);
120
        }
121
    }
122
123
    protected function initAutoload()
124
    {
125
        $dir = dirname(dirname(dirname(__DIR__)));
126
        require_once "$dir/autoload.php";
127
    }
128
129
    protected function scanPackages()
130
    {
131
        foreach ($this->getPackages() as $package) {
132
            if ($package->isComplete()) {
133
                $this->processPackage($package);
134
            }
135
        }
136
    }
137
138
    protected function reorderFiles(): void
139
    {
140
        foreach (array_keys($this->files) as $name) {
141
            $this->files[$name] = $this->getAllFiles($name);
142
        }
143
        foreach ($this->files as $name => $files) {
144
            $this->files[$name] = $this->orderFiles($files);
145
        }
146
    }
147
148
    protected function getAllFiles(string $name, array $stack = []): array
149
    {
150
        if (empty($this->files[$name])) {
151
            return[];
152
        }
153
        $res = [];
154
        foreach ($this->files[$name] as $file) {
155
            if (strncmp($file, '$', 1) === 0) {
156
                if (!in_array($name, $stack, true)) {
157
                    $res = array_merge($res, $this->getAllFiles(substr($file, 1), array_merge($stack, [$name])));
158
                }
159
            } else {
160
                $res[] = $file;
161
            }
162
        }
163
164
        return $res;
165
    }
166
167
    protected function orderFiles(array $files): array
168
    {
169
        if (empty($files)) {
170
            return [];
171
        }
172
        $keys = array_combine($files, $files);
173
        $res = [];
174
        foreach ($this->orderedFiles as $file) {
175
            if (isset($keys[$file])) {
176
                $res[$file] = $file;
177
            }
178
        }
179
180
        return array_values($res);
181
    }
182
183
    /**
184
     * Scans the given package and collects packages data.
185
     * @param Package $package
186
     */
187
    protected function processPackage(Package $package)
188
    {
189
        $files = $package->getFiles();
190
        $this->originalFiles[$package->getPrettyName()] = $files;
191
192
        if (!empty($files)) {
193
            $this->addFiles($package, $files);
194
        }
195
        if ($package->isRoot()) {
196
            $this->rootPackage = $package;
197
            $this->loadDotEnv($package);
198
            $devFiles = $package->getDevFiles();
199
            if (!empty($devFiles)) {
200
                $this->addFiles($package, $devFiles);
201
            }
202
            $this->outputDir = $package->getOutputDir();
203
            $alternatives = $package->getAlternatives();
204
            if (is_string($alternatives)) {
0 ignored issues
show
introduced by
The condition is_string($alternatives) is always false.
Loading history...
205
                $this->alternatives = $this->readConfig($package, $alternatives);
206
            } elseif (is_array($alternatives)) {
0 ignored issues
show
introduced by
The condition is_array($alternatives) is always true.
Loading history...
207
                $this->alternatives = $alternatives;
208
            } elseif (!empty($alternatives)) {
209
                throw new BadConfigurationException('alternatives must be array or path to configuration file');
210
            }
211
        }
212
213
        $aliases = $package->collectAliases();
214
215
        $this->builder->mergeAliases($aliases);
216
        $this->builder->setPackage($package->getPrettyName(), array_filter([
217
            'name' => $package->getPrettyName(),
218
            'version' => $package->getVersion(),
219
            'reference' => $package->getSourceReference() ?: $package->getDistReference(),
220
            'aliases' => $aliases,
221
        ]));
222
    }
223
224
    private function readConfig($package, $file): array
225
    {
226
        $path = $package->preparePath($file);
227
        if (!file_exists($path)) {
228
            throw new FailedReadException("failed read file: $file");
229
        }
230
        $reader = ReaderFactory::get($this->builder, $path);
231
232
        return $reader->read($path);
233
234
    }
235
236
    protected function loadDotEnv(Package $package)
237
    {
238
        $path = $package->preparePath('.env');
239
        if (file_exists($path) && class_exists('Dotenv\Dotenv')) {
240
            $this->addFile($package, 'dotenv', $path);
241
        }
242
    }
243
244
    /**
245
     * Adds given files to the list of files to be processed.
246
     * Prepares `defines` in reversed order (outer package first) because
247
     * constants cannot be redefined.
248
     * @param Package $package
249
     * @param array $files
250
     */
251
    protected function addFiles(Package $package, array $files)
252
    {
253
        foreach ($files as $name => $paths) {
254
            $paths = (array) $paths;
255
            if ('defines' === $name) {
256
                $paths = array_reverse($paths);
257
            }
258
            foreach ($paths as $path) {
259
                $this->addFile($package, $name, $path);
260
            }
261
        }
262
    }
263
264
    protected $orderedFiles = [];
265
266
    protected function addFile(Package $package, string $name, string $path)
267
    {
268
        $path = $package->preparePath($path);
269
        if (!isset($this->files[$name])) {
270
            $this->files[$name] = [];
271
        }
272
        if (in_array($path, $this->files[$name], true)) {
273
            return;
274
        }
275
        if ('defines' === $name) {
276
            array_unshift($this->orderedFiles, $path);
277
            array_unshift($this->files[$name], $path);
278
        } else {
279
            array_push($this->orderedFiles, $path);
280
            array_push($this->files[$name], $path);
281
        }
282
    }
283
284
    /**
285
     * Sets [[packages]].
286
     * @param Package[] $packages
287
     */
288
    public function setPackages(array $packages)
289
    {
290
        $this->packages = $packages;
291
    }
292
293 2
    /**
294
     * Gets [[packages]].
295 2
     * @return Package[]
296 2
     */
297
    public function getPackages()
298
    {
299
        if (null === $this->packages) {
300
            $this->packages = $this->findPackages();
301
        }
302 1
303
        return $this->packages;
304 1
    }
305
306
    /**
307
     * Plain list of all project dependencies (including nested) as provided by composer.
308 1
     * The list is unordered (chaotic, can be different after every update).
309
     */
310
    protected $plainList = [];
311
312
    /**
313
     * Ordered list of package in form: package => depth
314
     * For order description @see findPackages.
315
     */
316
    protected $orderedList = [];
317
318
    /**
319
     * Returns ordered list of packages:
320
     * - listed earlier in the composer.json will get earlier in the list
321
     * - childs before parents.
322
     * @return Package[]
323
     */
324
    public function findPackages()
325
    {
326
        $root = new Package($this->composer->getPackage(), $this->composer);
327
        $this->plainList[$root->getPrettyName()] = $root;
328
        foreach ($this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages() as $package) {
329
            $this->plainList[$package->getPrettyName()] = new Package($package, $this->composer);
330
        }
331
        $this->orderedList = [];
332
        $this->iteratePackage($root, true);
333
334
        $res = [];
335
        foreach (array_keys($this->orderedList) as $name) {
336
            $res[] = $this->plainList[$name];
337
        }
338
339
        return $res;
340
    }
341
342
    /**
343
     * Iterates through package dependencies.
344
     * @param Package $package to iterate
345
     * @param bool $includingDev process development dependencies, defaults to not process
346
     */
347
    protected function iteratePackage(Package $package, $includingDev = false)
348
    {
349
        $name = $package->getPrettyName();
350
351
        /// prevent infinite loop in case of circular dependencies
352
        static $processed = [];
353
        if (isset($processed[$name])) {
354
            return;
355
        } else {
356
            $processed[$name] = 1;
357
        }
358
359
        /// package depth in dependency hierarchy
360
        static $depth = 0;
361
        ++$depth;
362
363
        $this->iterateDependencies($package);
364
        if ($includingDev) {
365
            $this->iterateDependencies($package, true);
366
        }
367
        if (!isset($this->orderedList[$name])) {
368
            $this->orderedList[$name] = $depth;
369
        }
370
371
        --$depth;
372
    }
373
374
    /**
375
     * Iterates dependencies of the given package.
376
     * @param Package $package
377
     * @param bool $dev which dependencies to iterate: true - dev, default - general
378
     */
379
    protected function iterateDependencies(Package $package, $dev = false)
380
    {
381
        $deps = $dev ? $package->getDevRequires() : $package->getRequires();
382
        foreach (array_keys($deps) as $target) {
383
            if (isset($this->plainList[$target]) && empty($this->orderedList[$target])) {
384
                $this->iteratePackage($this->plainList[$target]);
385
            }
386
        }
387
    }
388
389
    protected function showDepsTree()
390
    {
391
        if (!$this->io->isVerbose()) {
392
            return;
393
        }
394
395
        foreach (array_reverse($this->orderedList) as $name => $depth) {
396
            $deps = $this->originalFiles[$name];
397
            $color = $this->colors[$depth % count($this->colors)];
398
            $indent = str_repeat('   ', $depth - 1);
399
            $package = $this->plainList[$name];
400
            $showdeps = $deps ? '<comment>[' . implode(',', array_keys($deps)) . ']</>' : '';
401
            $this->io->write(sprintf('%s - <fg=%s;options=bold>%s</> %s %s', $indent, $color, $name, $package->getFullPrettyVersion(), $showdeps));
402
        }
403
    }
404
}
405