Completed
Push — master ( b85299...2a4a53 )
by Andrii
01:53
created

Plugin::getBaseDir()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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