Completed
Push — master ( 4555b5...c98f1b )
by Andrii
05:29
created

Plugin::initAutoload()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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-2017, 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\Package\CompletePackageInterface;
17
use Composer\Package\PackageInterface;
18
use Composer\Package\RootPackageInterface;
19
use Composer\Plugin\PluginInterface;
20
use Composer\Script\Event;
21
use Composer\Script\ScriptEvents;
22
use Composer\Util\Filesystem;
23
24
/**
25
 * Plugin class.
26
 *
27
 * @author Andrii Vasyliev <[email protected]>
28
 */
29
class Plugin implements PluginInterface, EventSubscriberInterface
30
{
31
    const YII2_PACKAGE_TYPE = 'yii2-extension';
32
    const EXTRA_OPTION_NAME = 'config-plugin';
33
34
    /**
35
     * @var PackageInterface[] the array of active composer packages
36
     */
37
    protected $packages;
38
39
    /**
40
     * @var string absolute path to the package base directory
41
     */
42
    protected $baseDir;
43
44
    /**
45
     * @var string absolute path to vendor directory
46
     */
47
    protected $vendorDir;
48
49
    /**
50
     * @var Filesystem utility
51
     */
52
    protected $filesystem;
53
54
    /**
55
     * @var array config name => list of files
56
     */
57
    protected $files = [
58
        'defines' => [],
59
        'params'  => [],
60
    ];
61
62
    protected $aliases = [];
63
64
    protected $extensions = [];
65
66
    /**
67
     * @var array array of not yet merged params
68
     */
69
    protected $rawParams = [];
70
71
    /**
72
     * @var Composer instance
73
     */
74
    protected $composer;
75
76
    /**
77
     * @var IOInterface
78
     */
79
    public $io;
80
81
    /**
82
     * Initializes the plugin object with the passed $composer and $io.
83
     * @param Composer $composer
84
     * @param IOInterface $io
85
     */
86 2
    public function activate(Composer $composer, IOInterface $io)
87
    {
88 2
        $this->composer = $composer;
89 2
        $this->io = $io;
90 2
    }
91
92
    /**
93
     * Returns list of events the plugin is subscribed to.
94
     * @return array list of events
95
     */
96 1
    public static function getSubscribedEvents()
97
    {
98
        return [
99 1
            ScriptEvents::POST_AUTOLOAD_DUMP => [
100
                ['onPostAutoloadDump', 0],
101 1
            ],
102
        ];
103
    }
104
105
    /**
106
     * This is the main function.
107
     * @param Event $event
108
     */
109
    public function onPostAutoloadDump(Event $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

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

Loading history...
110
    {
111
        $this->io->writeError('<info>Assembling config files</info>');
112
        $this->initAutoload();
113
        $this->scanPackages();
114
115
        $builder = new Builder($this->files);
116
        $builder->setAddition(['aliases' => $this->aliases]);
117
        $builder->setIo($this->io);
118
        $builder->saveFiles();
119
        $builder->writeConfig('aliases', $this->aliases);
120
        $builder->writeConfig('extensions', $this->extensions);
121
        $builder->buildConfigs();
122
    }
123
124
    protected function initAutoload()
125
    {
126
        require_once dirname(dirname(dirname(__DIR__))) . '/autoload.php';
127
    }
128
129
    protected function scanPackages()
130
    {
131
        foreach ($this->getPackages() as $package) {
132
            if ($package instanceof CompletePackageInterface) {
133
                $this->processPackage($package);
134
            }
135
        }
136
    }
137
138
    /**
139
     * Scans the given package and collects extensions data.
140
     * @param PackageInterface $package
141
     */
142
    protected function processPackage(CompletePackageInterface $package)
143
    {
144
        $extra = $package->getExtra();
145
        $files = isset($extra[self::EXTRA_OPTION_NAME]) ? $extra[self::EXTRA_OPTION_NAME] : null;
146
147
        if ($package->getType() !== self::YII2_PACKAGE_TYPE && is_null($files)) {
148
            return;
149
        }
150
151
        if (is_array($files)) {
152
            $this->processFiles($package, $files);
153
        }
154
155
        $aliases = $this->collectAliases($package);
156
        $this->aliases = array_merge($this->aliases, $aliases);
157
158
        $this->extensions[$package->getPrettyName()] = array_filter([
159
            'name' => $package->getPrettyName(),
160
            'version' => $package->getVersion(),
161
            'reference' => $package->getSourceReference() ?: $package->getDistReference(),
162
            'aliases' => $aliases,
163
        ]);
164
    }
165
166
    protected function processFiles(CompletePackageInterface $package, array $files)
167
    {
168
        foreach ($files as $name => $paths) {
169
            foreach ((array) $paths as $path) {
170
                if (!isset($this->files[$name])) {
171
                    $this->files[$name] = [];
172
                }
173
                array_push($this->files[$name], $this->preparePath($package, $path));
174
            }
175
        }
176
    }
177
178
    /**
179
     * Collects package aliases.
180
     * @param CompletePackageInterface $package
181
     * @return array collected aliases
182
     */
183
    protected function collectAliases(CompletePackageInterface $package)
184
    {
185
        $aliases = array_merge(
186
            $this->prepareAliases($package, 'psr-0'),
187
            $this->prepareAliases($package, 'psr-4')
188
        );
189
        if ($package instanceof RootPackageInterface) {
190
            $aliases = array_merge($aliases,
191
                $this->prepareAliases($package, 'psr-0', true),
192
                $this->prepareAliases($package, 'psr-4', true)
193
            );
194
        }
195
196
        return $aliases;
197
    }
198
199
    /**
200
     * Prepare aliases.
201
     * @param PackageInterface $package
202
     * @param string 'psr-0' or 'psr-4'
203
     * @return array
204
     */
205
    protected function prepareAliases(PackageInterface $package, $psr, $dev = false)
206
    {
207
        $autoload = $dev ? $package->getDevAutoload() : $package->getAutoload();
208
        if (empty($autoload[$psr])) {
209
            return [];
210
        }
211
212
        $aliases = [];
213
        foreach ($autoload[$psr] as $name => $path) {
214
            if (is_array($path)) {
215
                // ignore psr-4 autoload specifications with multiple search paths
216
                // we can not convert them into aliases as they are ambiguous
217
                continue;
218
            }
219
            $name = str_replace('\\', '/', trim($name, '\\'));
220
            $path = $this->preparePath($package, $path);
221
            if ('psr-0' === $psr) {
222
                $path .= '/' . $name;
223
            }
224
            $aliases["@$name"] = $path;
225
        }
226
227
        return $aliases;
228
    }
229
230
    /**
231
     * Builds path inside of a package.
232
     * @param PackageInterface $package
233
     * @param mixed $path can be absolute or relative
234
     * @return string absolute paths will stay untouched
235
     */
236
    public function preparePath(PackageInterface $package, $path)
237
    {
238
        $skippable = strncmp($path, '?', 1) === 0 ? '?' : '';
239
        if ($skippable) {
240
            $path = substr($path, 1);
241
        }
242
243
        if (strncmp($path, '$', 1) === 0) {
244
            $path = Builder::path(substr($path, 1));
245
        }
246
247
        if (!$this->getFilesystem()->isAbsolutePath($path)) {
248
            $prefix = $package instanceof RootPackageInterface
249
                ? $this->getBaseDir()
250
                : $this->getVendorDir() . '/' . $package->getPrettyName();
251
            $path = $prefix . '/' . $path;
252
        }
253
254
        return $skippable . $this->getFilesystem()->normalizePath($path);
255 2
    }
256
257 2
    /**
258 2
     * Sets [[packages]].
259
     * @param PackageInterface[] $packages
260
     */
261
    public function setPackages(array $packages)
262
    {
263
        $this->packages = $packages;
264 1
    }
265
266 1
    /**
267
     * Gets [[packages]].
268
     * @return \Composer\Package\PackageInterface[]
269
     */
270 1
    public function getPackages()
271
    {
272
        if ($this->packages === null) {
273
            $this->packages = $this->findPackages();
274
        }
275
276
        return $this->packages;
277
    }
278
279
    /**
280
     * Plain list of all project dependencies (including nested) as provided by composer.
281
     * The list is unordered (chaotic, can be different after every update).
282
     */
283
    protected $plainList = [];
284
285
    /**
286
     * Ordered list of package. Order @see findPackages.
287
     */
288
    protected $orderedList = [];
289
290
    /**
291
     * Returns ordered list of packages:
292
     * - listed earlier in the composer.json will get earlier in the list
293
     * - childs before parents.
294
     * @return \Composer\Package\PackageInterface[]
295
     */
296
    public function findPackages()
297
    {
298
        $root = $this->composer->getPackage();
299
        $this->plainList[$root->getPrettyName()] = $root;
300
        foreach ($this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages() as $package) {
301
            $this->plainList[$package->getPrettyName()] = $package;
302
        }
303
        $this->orderedList = [];
304
        $this->iteratePackage($root, true);
305
306
        if ($this->io->isVerbose()) {
307
            $indent = ' - ';
308
            $packages = $indent . implode("\n$indent", $this->orderedList);
309
            $this->io->writeError($packages);
310
        }
311
        $res = [];
312
        foreach ($this->orderedList as $name) {
313
            $res[] = $this->plainList[$name];
314
        }
315
316
        return $res;
317
    }
318
319
    /**
320
     * Iterates through package dependencies.
321
     * @param PackageInterface $package to iterate
322
     * @param bool $includingDev process development dependencies, defaults to not process
323
     */
324
    public function iteratePackage(PackageInterface $package, $includingDev = false)
325
    {
326
        $name = $package->getPrettyName();
327
328
        /// prevent infinite loop in case of circular dependencies
329
        static $processed = [];
330
        if (isset($processed[$name])) {
331
            return;
332
        } else {
333
            $processed[$name] = 1;
334
        }
335
336
        $this->iterateDependencies($package);
337
        if ($includingDev) {
338
            $this->iterateDependencies($package, true);
339
        }
340
        if (!isset($this->orderedList[$name])) {
341
            $this->orderedList[$name] = $name;
342
        }
343
    }
344
345
    /**
346
     * Iterates dependencies of the given package.
347
     * @param PackageInterface $package
348
     * @param bool $dev which dependencies to iterate: true - dev, default - general
349
     */
350
    public function iterateDependencies(PackageInterface $package, $dev = false)
351
    {
352
        $path = $this->preparePath($package, 'composer.json');
353
        if (file_exists($path)) {
354
            $conf = json_decode(file_get_contents($path), true);
355
            $what = $dev ? 'require-dev' : 'require';
356
            $deps = isset($conf[$what]) ? $conf[$what] : [];
357
        } else {
358
            $deps = $dev ? $package->getDevRequires() : $package->getRequires();
359
        }
360
        foreach (array_keys($deps) as $target) {
361
            if (isset($this->plainList[$target]) && !isset($this->orderedList[$target])) {
362
                $this->iteratePackage($this->plainList[$target]);
363
            }
364
        }
365
    }
366
367
    /**
368
     * Get absolute path to package base dir.
369
     * @return string
370
     */
371
    public function getBaseDir()
372
    {
373
        if ($this->baseDir === null) {
374
            $this->baseDir = dirname($this->getVendorDir());
375
        }
376
377
        return $this->baseDir;
378
    }
379
380
    /**
381
     * Get absolute path to composer vendor dir.
382
     * @return string
383
     */
384
    public function getVendorDir()
385
    {
386
        if ($this->vendorDir === null) {
387
            $dir = $this->composer->getConfig()->get('vendor-dir', '/');
388
            $this->vendorDir = $this->getFilesystem()->normalizePath($dir);
389
        }
390
391
        return $this->vendorDir;
392
    }
393
394
    /**
395
     * Getter for filesystem utility.
396
     * @return Filesystem
397
     */
398
    public function getFilesystem()
399
    {
400
        if ($this->filesystem === null) {
401
            $this->filesystem = new Filesystem();
402
        }
403
404
        return $this->filesystem;
405
    }
406
}
407