Completed
Push — master ( 65d1a3...ec3bda )
by Andrii
02:43
created

Plugin::getOutputDir()   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 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
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\ComposerConfigPlugin;
13
14
use Composer\Composer;
15
use Composer\EventDispatcher\EventSubscriberInterface;
16
use Composer\IO\IOInterface;
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 PACKAGE_TYPE          = 'yii2-extension';
32
    const EXTRA_OPTION_NAME     = 'config-plugin';
33
    const OUTPUT_PATH           = 'hiqdev/config';
34
    const BASE_DIR_SAMPLE       = '<base-dir>';
35
    const VENDOR_DIR_SAMPLE     = '<base-dir>/vendor';
36
37
    /**
38
     * @var PackageInterface[] the array of active composer packages
39
     */
40
    protected $packages;
41
42
    /**
43
     * @var string absolute path to the package base directory.
44
     */
45
    protected $baseDir;
46
47
    /**
48
     * @var string absolute path to vendor directory.
49
     */
50
    protected $vendorDir;
51
52
    /**
53
     * @var Filesystem utility
54
     */
55
    protected $filesystem;
56
57
    /**
58
     * @var array assembled config data
59
     */
60
    protected $data = [
61
        'aliases'    => [],
62
        'extensions' => [],
63
    ];
64
65
    /**
66
     * @var array raw collected data
67
     */
68
    protected $raw = [];
69
70
    /**
71
     * @var array array of not yet merged params
72
     */
73
    protected $rawParams = [];
74
75
    /**
76
     * @var Composer instance
77
     */
78
    protected $composer;
79
80
    /**
81
     * @var IOInterface
82
     */
83
    public $io;
84
85
    /**
86
     * Initializes the plugin object with the passed $composer and $io.
87
     * @param Composer $composer
88
     * @param IOInterface $io
89
     */
90 2
    public function activate(Composer $composer, IOInterface $io)
91
    {
92 2
        $this->composer = $composer;
93 2
        $this->io = $io;
94 2
    }
95
96
    /**
97
     * Returns list of events the plugin is subscribed to.
98
     * @return array list of events
99
     */
100 1
    public static function getSubscribedEvents()
101
    {
102
        return [
103 1
            ScriptEvents::POST_AUTOLOAD_DUMP => [
104
                ['onPostAutoloadDump', 0],
105 1
            ],
106
        ];
107
    }
108
109
    /**
110
     * This is the main function.
111
     * @param Event $event
112
     */
113
    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...
114
    {
115
        $this->io->writeError('<info>Assembling config files</info>');
116
117
        /// scan packages
118
        foreach ($this->getPackages() as $package) {
119
            if ($package instanceof \Composer\Package\CompletePackageInterface) {
120
                $this->processPackage($package);
121
            }
122
        }
123
        $this->processPackage($this->composer->getPackage());
124
125
        $this->assembleParams();
126
        define('COMPOSER_CONFIG_PLUGIN_DIR', $this->getOutputDir());
127
        $this->assembleConfigs();
128
    }
129
130
    /**
131
     * Scans the given package and collects extensions data.
132
     * @param PackageInterface $package
133
     */
134
    public function processPackage(PackageInterface $package)
135
    {
136
        $extra = $package->getExtra();
137
        $files = isset($extra[self::EXTRA_OPTION_NAME]) ? $extra[self::EXTRA_OPTION_NAME] : null;
138
        if ($package->getType() !== self::PACKAGE_TYPE && is_null($files)) {
139
            return;
140
        }
141
142
        $extension = [
143
            'name'    => $package->getName(),
144
            'version' => $package->getVersion(),
145
        ];
146
        if ($package->getVersion() === '9999999-dev') {
147
            $reference = $package->getSourceReference() ?: $package->getDistReference();
148
            if ($reference) {
149
                $extension['reference'] = $reference;
150
            }
151
        }
152
153
        $aliases = array_merge(
154
            $this->prepareAliases($package, 'psr-0'),
155
            $this->prepareAliases($package, 'psr-4')
156
        );
157
158
        if (isset($files['defines'])) {
159
            $this->readConfigFile($package, $files['defines']);
160
            unset($files['defines']);
161
        }
162
163
        if (isset($files['params'])) {
164
            $this->rawParams[] = $this->readConfigFile($package, $files['params']);
165
            unset($files['params']);
166
        }
167
168
        $this->raw[$package->getName()] = [
169
            'package'   => $package,
170
            'extension' => $extension,
171
            'aliases'   => $aliases,
172
            'files'     => (array) $files,
173
        ];
174
    }
175
176
    public function assembleParams()
177
    {
178
        $this->assembleFile('params', $this->rawParams);
179
    }
180
181
    public function assembleConfigs()
182
    {
183
        $allAliases = [];
184
        $extensions = [];
185
        $rawConfigs = [];
186
        foreach ($this->raw as $name => $info) {
187
            $extensions[$name] = $info['extension'];
188
189
            $aliases = $info['aliases'];
190
            $allAliases = array_merge($allAliases, $aliases);
191
192
            foreach ($info['files'] as $file => $path) {
193
                $config = $this->readConfigFile($info['package'], $path);
194
                $config['aliases'] = array_merge(
195
                    $aliases,
196
                    isset($config['aliases']) ? (array) $config['aliases'] : []
197
                );
198
                $allAliases = array_merge($allAliases, $config['aliases']);
199
                $rawConfigs[$file][] = $config;
200
            }
201
        }
202
203
        $this->writeFile('aliases',    $allAliases);
204
        $this->writeFile('extensions', $extensions);
205
206
        foreach ($rawConfigs as $file => $configs) {
207
            $this->assembleFile($file, $configs);
208
        }
209
    }
210
211
    protected function assembleFile($file, $configs)
212
    {
213
        $data = call_user_func_array([Helper::class, 'mergeConfig'], $configs);
214
        $this->writeFile($file, $data);
215
    }
216
217
    /**
218
     * Read extra config.
219
     * @param string $file
220
     * @return array
221
     */
222
    protected function readConfigFile(PackageInterface $package, $file)
223
    {
224
        $path = $this->preparePath($package, $file);
225
        if (!file_exists($path)) {
226
            $this->io->writeError('<error>Non existent extension config file</error> ' . $file . ' in ' . $package->getName());
227
            exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method readConfigFile() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
228
        }
229
        return require $path;
230
    }
231
232
    /**
233
     * Prepare aliases.
234
     *
235
     * @param PackageInterface $package
236
     * @param string 'psr-0' or 'psr-4'
237
     * @return array
238
     */
239
    protected function prepareAliases(PackageInterface $package, $psr)
240
    {
241
        $autoload = $package->getAutoload();
242
        if (empty($autoload[$psr])) {
243
            return [];
244
        }
245
246
        $aliases = [];
247
        foreach ($autoload[$psr] as $name => $path) {
248
            if (is_array($path)) {
249
                // ignore psr-4 autoload specifications with multiple search paths
250
                // we can not convert them into aliases as they are ambiguous
251
                continue;
252
            }
253
            $name = str_replace('\\', '/', trim($name, '\\'));
254
            $path = $this->preparePath($package, $path);
255
            $path = $this->substitutePath($path, $this->getBaseDir(), self::BASE_DIR_SAMPLE);
256
            if ('psr-0' === $psr) {
257
                $path .= '/' . $name;
258
            }
259
            $aliases["@$name"] = $path;
260
        }
261
262
        return $aliases;
263
    }
264
265
    /**
266
     * Substitute path with alias if applicable.
267
     * @param string $path
268
     * @param string $dir
269
     * @param string $alias
270
     * @return string
271
     */
272
    public function substitutePath($path, $dir, $alias)
273
    {
274
        return (substr($path, 0, strlen($dir) + 1) === $dir . '/') ? $alias . substr($path, strlen($dir)) : $path;
275
    }
276
277
    public function preparePath(PackageInterface $package, $path)
278
    {
279
        if (!$this->getFilesystem()->isAbsolutePath($path)) {
280
            $prefix = $package instanceof RootPackageInterface ? $this->getBaseDir() : $this->getVendorDir() . '/' . $package->getPrettyName();
281
            $path = $prefix . '/' . $path;
282
        }
283
284
        return $this->getFilesystem()->normalizePath($path);
285
    }
286
287
    /**
288
     * Get output dir.
289
     * @return string
290
     */
291
    public function getOutputDir()
292
    {
293
        return $this->getVendorDir() . DIRECTORY_SEPARATOR . static::OUTPUT_PATH;
294
    }
295
296
    /**
297
     * Build full path to write file for a given filename.
298
     * @param string $filename
299
     * @return string
300
     */
301
    public function buildOutputPath($filename)
302
    {
303
        return $this->getOutputDir() . DIRECTORY_SEPARATOR . $filename . '.php';
304
    }
305
306
    /**
307
     * Writes file.
308
     * @param string $filename
309
     * @param array $data
310
     */
311
    protected function writeFile($filename, array $data)
312
    {
313
        $path = $this->buildOutputPath($filename);
314
        if (!file_exists(dirname($path))) {
315
            mkdir(dirname($path), 0777, true);
316
        }
317
        $array = str_replace("'" . self::BASE_DIR_SAMPLE, '$baseDir . \'', Helper::exportVar($data));
318
        file_put_contents($path, "<?php\n\n\$baseDir = dirname(dirname(__DIR__));\n\nreturn $array;\n");
319
    }
320
321
    /**
322
     * Sets [[packages]].
323
     * @param PackageInterface[] $packages
324
     */
325 2
    public function setPackages(array $packages)
326
    {
327 2
        $this->packages = $packages;
328 2
    }
329
330
    /**
331
     * Gets [[packages]].
332
     * @return \Composer\Package\PackageInterface[]
333
     */
334 1
    public function getPackages()
335
    {
336 1
        if ($this->packages === null) {
337
            $this->packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
338
        }
339
340 1
        return $this->packages;
341
    }
342
343
    /**
344
     * Get absolute path to package base dir.
345
     * @return string
346
     */
347
    public function getBaseDir()
348
    {
349
        if ($this->baseDir === null) {
350
            $this->baseDir = dirname($this->getVendorDir());
351
        }
352
353
        return $this->baseDir;
354
    }
355
356
    /**
357
     * Get absolute path to composer vendor dir.
358
     * @return string
359
     */
360
    public function getVendorDir()
361
    {
362
        if ($this->vendorDir === null) {
363
            $dir = $this->composer->getConfig()->get('vendor-dir', '/');
364
            $this->vendorDir = $this->getFilesystem()->normalizePath($dir);
365
        }
366
367
        return $this->vendorDir;
368
    }
369
370
    /**
371
     * Getter for filesystem utility.
372
     * @return Filesystem
373
     */
374
    public function getFilesystem()
375
    {
376
        if ($this->filesystem === null) {
377
            $this->filesystem = new Filesystem();
378
        }
379
380
        return $this->filesystem;
381
    }
382
}
383