Plugin::scanPackages()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

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