Completed
Push — master ( ab86c3...b85299 )
by Andrii
01:45
created

Plugin::assembleParams()   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
rs 10
c 0
b 0
f 0
ccs 0
cts 1
cp 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
/*
4
 * Composer plugin for config assembling
5
 *
6
 * @link      https://github.com/hiqdev/composer-config-plugin
7
 * @package   composer-config-plugin
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hiqdev\composer\config;
13
14
use Composer\Composer;
15
use Composer\EventDispatcher\EventSubscriberInterface;
16
use Composer\IO\IOInterface;
17
use Composer\Package\PackageInterface;
18
use Composer\Package\CompletePackageInterface;
19
use Composer\Package\RootPackageInterface;
20
use Composer\Plugin\PluginInterface;
21
use Composer\Script\Event;
22
use Composer\Script\ScriptEvents;
23
use Composer\Util\Filesystem;
24
25
/**
26
 * Plugin class.
27
 *
28
 * @author Andrii Vasyliev <[email protected]>
29
 */
30
class Plugin implements PluginInterface, EventSubscriberInterface
31
{
32
    const OUTPUT_DIR = 'config';
33
    const YII2_PACKAGE_TYPE = 'yii2-extension';
34
    const EXTRA_OPTION_NAME = 'config-plugin';
35
36
    /**
37
     * @var PackageInterface[] the array of active composer packages
38
     */
39
    protected $packages;
40
41
    /**
42
     * @var string absolute path to the package base directory
43
     */
44
    protected $baseDir;
45
46
    /**
47
     * @var string absolute path to vendor directory
48
     */
49
    protected $vendorDir;
50
51
    /**
52
     * @var Filesystem utility
53
     */
54
    protected $filesystem;
55
56
    /**
57
     * @var array config name => list of files
58
     */
59
    protected $files = [
60
        'defines' => [],
61
        'params'  => [],
62
    ];
63
64
    protected $aliases = [];
65
66
    protected $extensions = [];
67
68
    /**
69
     * @var array array of not yet merged params
70
     */
71
    protected $rawParams = [];
72
73
    /**
74
     * @var Composer instance
75
     */
76
    protected $composer;
77
78
    /**
79
     * @var IOInterface
80
     */
81
    public $io;
82
83
    /**
84
     * Initializes the plugin object with the passed $composer and $io.
85
     * @param Composer $composer
86
     * @param IOInterface $io
87
     */
88
    public function activate(Composer $composer, IOInterface $io)
89
    {
90 2
        $this->composer = $composer;
91
        $this->io = $io;
92 2
    }
93 2
94 2
    /**
95
     * Returns list of events the plugin is subscribed to.
96
     * @return array list of events
97
     */
98
    public static function getSubscribedEvents()
99
    {
100 1
        return [
101
            ScriptEvents::POST_AUTOLOAD_DUMP => [
102
                ['onPostAutoloadDump', 0],
103 1
            ],
104 1
        ];
105 1
    }
106 1
107
    /**
108
     * This is the main function.
109
     * @param Event $event
110
     */
111
    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...
112
    {
113
        $this->io->writeError('<info>Assembling config files</info>');
114
        $this->scanPackages();
115
116
        $builder = new Builder(static::getOutputDir(), $this->files);
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
    public static function rebuild()
125
    {
126
        $builder = new Builder(static::getOutputDir());
127
        $builder->loadFiles();
128
        $builder->buildConfigs();
129
    }
130
131
    protected function scanPackages()
132
    {
133
        foreach ($this->getPackages() as $package) {
134
            if ($package instanceof CompletePackageInterface) {
135
                $this->processPackage($package);
136
            }
137
        }
138
    }
139
140
    /**
141
     * Scans the given package and collects extensions data.
142
     * @param PackageInterface $package
143
     */
144
    protected function processPackage(PackageInterface $package)
145
    {
146
        $extra = $package->getExtra();
147
        $files = isset($extra[self::EXTRA_OPTION_NAME]) ? $extra[self::EXTRA_OPTION_NAME] : null;
148
149
        if ($package->getType() !== self::YII2_PACKAGE_TYPE && is_null($files)) {
150
            return;
151
        }
152
153
        foreach ($files as $name => $pathes) {
154
            foreach ((array) $pathes as $path) {
155
                if (!isset($this->files[$name])) {
156
                    $this->files[$name] = [];
157
                }
158
                array_push($this->files[$name], $this->preparePath($package, $path));
159
            }
160
        }
161
162
        $this->aliases = array_merge(
163
            $this->aliases,
164
            $this->prepareAliases($package, 'psr-0'),
165
            $this->prepareAliases($package, 'psr-4')
166
        );
167
168
        $this->extensions[$package->getPrettyName()] = array_filter([
169
            'name' => $package->getPrettyName(),
170
            'version' => $package->getVersion(),
171
            'reference' => $package->getSourceReference() ?: $package->getDistReference(),
172
        ]);
173
    }
174
175
    /**
176
     * Prepare aliases.
177
     * @param PackageInterface $package
178
     * @param string 'psr-0' or 'psr-4'
179
     * @return array
180
     */
181
    protected function prepareAliases(PackageInterface $package, $psr)
182
    {
183
        $autoload = $package->getAutoload();
184
        if (empty($autoload[$psr])) {
185
            return [];
186
        }
187
188
        $aliases = [];
189
        foreach ($autoload[$psr] as $name => $path) {
190
            if (is_array($path)) {
191
                // ignore psr-4 autoload specifications with multiple search paths
192
                // we can not convert them into aliases as they are ambiguous
193
                continue;
194
            }
195
            $name = str_replace('\\', '/', trim($name, '\\'));
196
            $path = $this->preparePath($package, $path);
197
            $path = $this->substitutePath($path, $this->getBaseDir(), Builder::BASE_DIR_SAMPLE);
198
            if ('psr-0' === $psr) {
199
                $path .= '/' . $name;
200
            }
201
            $aliases["@$name"] = $path;
202
        }
203
204
        return $aliases;
205
    }
206
207
    /**
208
     * Substitute path with alias if applicable.
209
     * @param string $path
210
     * @param string $dir
211
     * @param string $alias
212
     * @return string
213
     */
214
    protected function substitutePath($path, $dir, $alias)
215
    {
216
        return (substr($path, 0, strlen($dir) + 1) === $dir . '/') ? $alias . substr($path, strlen($dir)) : $path;
217
    }
218
219
    /**
220
     * Builds path inside of a package.
221
     * @param PackageInterface $package
222
     * @param mixed $path can be absolute or relative
223
     * @return string absolute pathes will stay untouched
224
     */
225
    public function preparePath(PackageInterface $package, $path)
226
    {
227
        $skippable = false;
228
        if (strncmp($path, '?', 1) === 0) {
229
            $skippable = true;
230
            $path = substr($path, 1);
231
        }
232
        if (!$this->getFilesystem()->isAbsolutePath($path)) {
233
            $prefix = $package instanceof RootPackageInterface
234
                ? $this->getBaseDir()
235
                : $this->getVendorDir() . '/' . $package->getPrettyName()
236
            ;
237
            $path = $prefix . '/' . $path;
238
        }
239
240
        return ($skippable ? '?' : '') . $this->getFilesystem()->normalizePath($path);
241
    }
242
243
    /**
244
     * Get output dir.
245
     * @return string
246
     */
247
    public static function getOutputDir()
248
    {
249
        return dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . static::OUTPUT_DIR;
250
    }
251
252
    /**
253
     * Returns full path to assembled config file.
254
     * @param string $filename name of config
255
     * @return string absolute path
256
     */
257
    public static function path($filename)
258
    {
259
        return static::getOutputDir() . DIRECTORY_SEPARATOR . $filename . '.php';
260
    }
261
262
    /**
263
     * Sets [[packages]].
264
     * @param PackageInterface[] $packages
265
     */
266
    public function setPackages(array $packages)
267
    {
268
        $this->packages = $packages;
269
    }
270
271
    /**
272
     * Gets [[packages]].
273
     * @return \Composer\Package\PackageInterface[]
274
     */
275
    public function getPackages()
276
    {
277
        if ($this->packages === null) {
278
            $this->packages = $this->findPackages();
279
        }
280
281
        return $this->packages;
282
    }
283
284
    /**
285
     * Plain list of all project dependencies (including nested) as provided by composer.
286
     * The list is unordered (chaotic, can be different after every update).
287
     */
288
    protected $plainList = [];
289
290
    /**
291
     * Ordered list of package. Order @see findPackages
292
     */
293
    protected $orderedList = [];
294
295
    /**
296
     * Returns ordered list of packages:
297
     * - listed earlier in the composer.json will get earlier in the list
298
     * - childs before parents.
299
     * @return \Composer\Package\PackageInterface[]
300
     */
301
    public function findPackages()
302
    {
303
        $root = $this->composer->getPackage();
304
        $this->plainList[$root->getPrettyName()] = $root;
305
        foreach ($this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages() as $package) {
306
            $this->plainList[$package->getPrettyName()] = $package;
307
        }
308
        $this->orderedList = [];
309
        $this->iteratePackage($root, true);
310
311
        if ($this->io->isVerbose())  {
312
            $indent = ' - ';
313
            $packages = $indent . implode("\n$indent", $this->orderedList);
314
            $this->io->writeError($packages);
315
        }
316
        $res = [];
317
        foreach ($this->orderedList as $name) {
318
            $res[] = $this->plainList[$name];
319
        }
320
321
        return $res;
322
    }
323
324
    /**
325
     * Iterates through package dependencies.
326
     * @param PackageInterface $package to iterate
327
     * @param bool $includingDev process development dependencies, defaults to not process
328
     */
329
    public function iteratePackage(PackageInterface $package, $includingDev = false)
330
    {
331
        $name = $package->getPrettyName();
332
333
        /// prevent infinite loop in case of circular dependencies
334
        static $processed = [];
335
        if (isset($processed[$name])) {
336
            return;
337
        } else {
338
            $processed[$name] = 1;
339
        }
340
341
        $this->iterateDependencies($package);
342
        if ($includingDev) {
343
            $this->iterateDependencies($package, true);
344
        }
345
        if (!isset($this->orderedList[$name])) {
346
            $this->orderedList[$name] = $name;
347
        }
348 2
    }
349
350 2
    /**
351 2
     * Iterates dependencies of the given package.
352
     * @param PackageInterface $package
353
     * @param bool $dev which dependencies to iterate: true - dev, default - general
354
     */
355
    public function iterateDependencies(PackageInterface $package, $dev = false)
356
    {
357 1
        $path = $this->preparePath($package, 'composer.json');
358
        if (file_exists($path)) {
359 1
            $conf = json_decode(file_get_contents($path), true);
360
            $what = $dev ? 'require-dev' : 'require';
361
            $deps = isset($conf[$what]) ? $conf[$what] : [];
362
        } else {
363 1
            $deps = $dev ? $package->getDevRequires() : $package->getRequires();
364
        }
365
        foreach (array_keys($deps) as $target) {
366
            if (isset($this->plainList[$target]) && !isset($this->orderedList[$target])) {
367
                $this->iteratePackage($this->plainList[$target]);
368
            }
369
        }
370
    }
371
372
    /**
373
     * Get absolute path to package base dir.
374
     * @return string
375
     */
376
    public function getBaseDir()
377
    {
378
        if ($this->baseDir === null) {
379
            $this->baseDir = dirname($this->getVendorDir());
380
        }
381
382
        return $this->baseDir;
383
    }
384
385
    /**
386
     * Get absolute path to composer vendor dir.
387
     * @return string
388
     */
389
    public function getVendorDir()
390
    {
391
        if ($this->vendorDir === null) {
392
            $dir = $this->composer->getConfig()->get('vendor-dir', '/');
393
            $this->vendorDir = $this->getFilesystem()->normalizePath($dir);
394
        }
395
396
        return $this->vendorDir;
397
    }
398
399
    /**
400
     * Getter for filesystem utility.
401
     * @return Filesystem
402
     */
403
    public function getFilesystem()
404
    {
405
        if ($this->filesystem === null) {
406
            $this->filesystem = new Filesystem();
407
        }
408
409
        return $this->filesystem;
410
    }
411
}
412