Completed
Push — master ( 4e4f6b...337813 )
by Andrii
12:30
created

Plugin::iterateDependencies()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
ccs 0
cts 1
cp 0
rs 9.6111
c 0
b 0
f 0
cc 5
nc 6
nop 2
crap 30
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
    const YII2_PACKAGE_TYPE = 'yii2-extension';
28
    const EXTRA_OPTION_NAME = 'config-plugin';
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
        'defines' => [],
41
        'params'  => [],
42
    ];
43
44
    /**
45
     * @var array package name => configs as listed in `composer.json`
46
     */
47
    protected $originalFiles = [];
48
49
    /**
50
     * @var Builder
51
     */
52
    protected $builder;
53
54
    /**
55
     * @var Composer instance
56
     */
57
    protected $composer;
58
59
    /**
60
     * @var IOInterface
61
     */
62
    public $io;
63
64
    /**
65
     * Initializes the plugin object with the passed $composer and $io.
66
     * @param Composer $composer
67
     * @param IOInterface $io
68
     */
69
    public function activate(Composer $composer, IOInterface $io)
70
    {
71
        $this->composer = $composer;
72
        $this->io = $io;
73
    }
74
75
    /**
76
     * Returns list of events the plugin is subscribed to.
77
     * @return array list of events
78
     */
79
    public static function getSubscribedEvents()
80
    {
81
        return [
82
            ScriptEvents::POST_AUTOLOAD_DUMP => [
83
                ['onPostAutoloadDump', 0],
84
            ],
85
        ];
86
    }
87
88 2
    /**
89
     * This is the main function.
90 2
     * @param Event $event
91 2
     */
92 2
    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

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