Completed
Push — master ( 0c8c82...83bc09 )
by Andrii
11:53
created

Plugin::reorderFiles()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
ccs 0
cts 5
cp 0
rs 10
c 0
b 0
f 0
cc 3
nc 4
nop 0
crap 12
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
20
/**
21
 * Plugin class.
22
 *
23
 * @author Andrii Vasyliev <[email protected]>
24
 */
25
class Plugin implements PluginInterface, EventSubscriberInterface
26
{
27
    private const EXTRA_OPTION_NAME = 'config-plugin';
28
    private const EXTRA_DEV_OPTION_NAME = 'config-plugin-dev';
29
30
    /**
31
     * @var Package[] the array of active composer packages
32
     */
33
    protected $packages;
34
35
    /**
36
     * @var array config name => list of files
37
     */
38
    protected $files = [
39
        'dotenv'  => [],
40
        'aliases' => [],
41
        'defines' => [],
42
        'params'  => [],
43
    ];
44
45
    protected $colors = ['red', 'green', 'yellow', 'cyan', 'magenta', 'blue'];
46
47
    /**
48
     * @var array package name => configs as listed in `composer.json`
49
     */
50
    protected $originalFiles = [];
51
52
    /**
53
     * @var Builder
54
     */
55
    protected $builder;
56
57
    /**
58
     * @var Composer instance
59
     */
60
    protected $composer;
61
62
    /**
63
     * @var IOInterface
64
     */
65
    public $io;
66
67
    /**
68
     * Initializes the plugin object with the passed $composer and $io.
69
     * @param Composer $composer
70
     * @param IOInterface $io
71
     */
72
    public function activate(Composer $composer, IOInterface $io)
73
    {
74
        $this->composer = $composer;
75
        $this->io = $io;
76
    }
77
78
    /**
79
     * Returns list of events the plugin is subscribed to.
80
     * @return array list of events
81
     */
82
    public static function getSubscribedEvents()
83
    {
84
        return [
85
            ScriptEvents::POST_AUTOLOAD_DUMP => [
86
                ['onPostAutoloadDump', 0],
87
            ],
88 2
        ];
89
    }
90 2
91 2
    /**
92 2
     * This is the main function.
93
     * @param Event $event
94
     */
95
    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

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