Completed
Push — master ( 927a73...65d1a3 )
by Andrii
02:55
created

Plugin::getFilesystem()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 0
cts 5
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 6
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 1
                ['onPostAutoloadDump', 0],
105 1
            ],
106 1
        ];
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
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['params'])) {
159
            $this->rawParams[] = $this->readConfigFile($package, $files['params']);
160
            unset($files['params']);
161
        }
162
163
        $this->raw[$package->getName()] = [
164
            'package'   => $package,
165
            'extension' => $extensin,
0 ignored issues
show
Bug introduced by
The variable $extensin does not exist. Did you mean $extension?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
166
            'aliases'   => $aliases,
167
            'files'     => (array)$files,
168
        ];
169
    }
170
171
    public function assembleParams()
172
    {
173
        var_dump($this->rawParams);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($this->rawParams); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
174
        die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method assembleParams() 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...
175
        $params = call_user_func_array(Helper::mergeConfig, $this->rawParams);
0 ignored issues
show
Unused Code introduced by
$params = call_user_func...fig, $this->rawParams); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
176
177
        $this->writeFile('params', $params);
178
    }
179
180
    public function assembleConfigs()
181
    {
182
        foreach ($this->raw as $name => $info) {
183
            $this->data['extensions'][$name] = $info['extension'];
184
185
            $aliases = $info['aliases'];
186
            $this->data['aliases'] = array_merge($this->data['aliases'], $aliases);
187
188
            foreach ($info['files'] as $name => $path) {
189
                $config = $this->readConfigFile($package, $path);
0 ignored issues
show
Bug introduced by
The variable $package does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
190
                $config['aliases'] = array_merge(
191
                    $aliases,
192
                    isset($config['aliases']) ? (array) $config['aliases'] : []
193
                );
194
                $this->data['aliases'] = array_merge($this->data['aliases'], $config['aliases']);
195
                $this->data[$name] = isset($this->data[$name]) ? Helper::mergeConfig($this->data[$name], $config) : $config;
196
            }
197
        }
198
199
        foreach ($this->data as $name => $data) {
200
            $this->writeFile($name, $data);
201
        }
202
    }
203
204
    /**
205
     * Read extra config.
206
     * @param string $file
207
     * @return array
208
     */
209
    protected function readConfigFile(PackageInterface $package, $file)
210
    {
211
        $path = $this->preparePath($package, $file);
212
        if (!file_exists($path)) {
213
            $this->io->writeError('<error>Non existent extension config file</error> ' . $file . ' in ' . $package->getName());
214
            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...
215
        }
216
        return require $path;
217
    }
218
219
    /**
220
     * Prepare aliases.
221
     *
222
     * @param PackageInterface $package
223
     * @param string 'psr-0' or 'psr-4'
224
     * @return array
225
     */
226
    protected function prepareAliases(PackageInterface $package, $psr)
227
    {
228
        $autoload = $package->getAutoload();
229
        if (empty($autoload[$psr])) {
230
            return [];
231
        }
232
233
        $aliases = [];
234
        foreach ($autoload[$psr] as $name => $path) {
235
            if (is_array($path)) {
236
                // ignore psr-4 autoload specifications with multiple search paths
237
                // we can not convert them into aliases as they are ambiguous
238
                continue;
239
            }
240
            $name = str_replace('\\', '/', trim($name, '\\'));
241
            $path = $this->preparePath($package, $path);
242
            $path = $this->substitutePath($path, $this->getBaseDir(), self::BASE_DIR_SAMPLE);
243
            if ('psr-0' === $psr) {
244
                $path .= '/' . $name;
245
            }
246
            $aliases["@$name"] = $path;
247
        }
248
249
        return $aliases;
250
    }
251
252
    /**
253
     * Substitute path with alias if applicable.
254
     * @param string $path
255
     * @param string $dir
256
     * @param string $alias
257
     * @return string
258
     */
259
    public function substitutePath($path, $dir, $alias)
260
    {
261
        return (substr($path, 0, strlen($dir) + 1) === $dir . '/') ? $alias . substr($path, strlen($dir)) : $path;
262
    }
263
264
    public function preparePath(PackageInterface $package, $path)
265
    {
266
        if (!$this->getFilesystem()->isAbsolutePath($path)) {
267
            $prefix = $package instanceof RootPackageInterface ? $this->getBaseDir() : $this->getVendorDir() . '/' . $package->getPrettyName();
268
            $path = $prefix . '/' . $path;
269
        }
270
271
        return $this->getFilesystem()->normalizePath($path);
272
    }
273
274
    /**
275
     * Build full path to write file for a given filename.
276
     * @param string $filename
277
     * @return string
278
     */
279
    public function buildOutputPath($filename)
280
    {
281
        return $this->getVendorDir() . DIRECTORY_SEPARATOR . static::OUTPUT_PATH . DIRECTORY_SEPARATOR . $filename . '.php';
282
    }
283
284
    /**
285
     * Writes file.
286
     * @param string $filename
287
     * @param array $data
288
     */
289
    protected function writeFile($filename, array $data)
290
    {
291
        $path = $this->buildOutputPath($filename);
292
        if (!file_exists(dirname($path))) {
293
            mkdir(dirname($path), 0777, true);
294
        }
295
        $array = str_replace("'" . self::BASE_DIR_SAMPLE, '$baseDir . \'', Helper::exportVar($data));
296
        file_put_contents($path, "<?php\n\n\$baseDir = dirname(dirname(__DIR__));\n\nreturn $array;\n");
297
    }
298
299
    /**
300
     * Sets [[packages]].
301
     * @param PackageInterface[] $packages
302
     */
303 2
    public function setPackages(array $packages)
304
    {
305 2
        $this->packages = $packages;
306 2
    }
307
308
    /**
309
     * Gets [[packages]].
310
     * @return \Composer\Package\PackageInterface[]
311
     */
312 1
    public function getPackages()
313
    {
314 1
        if ($this->packages === null) {
315
            $this->packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
316
        }
317
318 1
        return $this->packages;
319
    }
320
321
    /**
322
     * Get absolute path to package base dir.
323
     * @return string
324
     */
325
    public function getBaseDir()
326
    {
327
        if ($this->baseDir === null) {
328
            $this->baseDir = dirname($this->getVendorDir());
329
        }
330
331
        return $this->baseDir;
332
    }
333
334
    /**
335
     * Get absolute path to composer vendor dir.
336
     * @return string
337
     */
338
    public function getVendorDir()
339
    {
340
        if ($this->vendorDir === null) {
341
            $dir = $this->composer->getConfig()->get('vendor-dir', '/');
342
            $this->vendorDir = $this->getFilesystem()->normalizePath($dir);
343
        }
344
345
        return $this->vendorDir;
346
    }
347
348
    /**
349
     * Getter for filesystem utility.
350
     * @return Filesystem
351
     */
352
    public function getFilesystem()
353
    {
354
        if ($this->filesystem === null) {
355
            $this->filesystem = new Filesystem();
356
        }
357
358
        return $this->filesystem;
359
    }
360
}
361