Completed
Push — master ( e7072d...ceda6d )
by Andrii
02:22
created

Plugin   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 11.54%

Importance

Changes 20
Bugs 3 Features 0
Metric Value
wmc 49
c 20
b 3
f 0
lcom 1
cbo 6
dl 0
loc 308
ccs 15
cts 130
cp 0.1154
rs 8.5454

16 Methods

Rating   Name   Duplication   Size   Complexity  
A activate() 0 5 1
A getSubscribedEvents() 0 8 1
A onPostAutoloadDump() 0 14 4
A buildOutputPath() 0 4 1
A saveFile() 0 9 2
D processPackage() 0 30 10
D mergeConfig() 0 25 9
A readExtensionConfig() 0 9 2
B prepareAliases() 0 25 5
A substitutePath() 0 4 2
A preparePath() 0 9 3
A setPackages() 0 4 1
A getPackages() 0 8 2
A getBaseDir() 0 8 2
A getVendorDir() 0 9 2
A getFilesystem() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Plugin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Plugin, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * Composer plugin for pluggable extensions
5
 *
6
 * @link      https://github.com/hiqdev/composer-extension-plugin
7
 * @package   composer-extension-plugin
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hiqdev\composerextensionplugin;
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     = 'extension-plugin';
33
    const OUTPUT_PATH           = 'hiqdev';
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 whole data
59
     */
60
    protected $data = [
61
        'extensions' => [],
62
    ];
63
64
    /**
65
     * @var Composer instance
66
     */
67
    protected $composer;
68
69
    /**
70
     * @var IOInterface
71
     */
72
    public $io;
73
74
    /**
75
     * Initializes the plugin object with the passed $composer and $io.
76
     * @param Composer $composer
77
     * @param IOInterface $io
78
     */
79 2
    public function activate(Composer $composer, IOInterface $io)
80
    {
81 2
        $this->composer = $composer;
82 2
        $this->io = $io;
83 2
    }
84
85
    /**
86
     * Returns list of events the plugin is subscribed to.
87
     * @return array list of events
88
     */
89 1
    public static function getSubscribedEvents()
90
    {
91
        return [
92 1
            ScriptEvents::POST_AUTOLOAD_DUMP => [
93 1
                ['onPostAutoloadDump', 0],
94 1
            ],
95 1
        ];
96
    }
97
98
    /**
99
     * Simply rewrites extensions file from scratch.
100
     * @param Event $event
101
     */
102
    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...
103
    {
104
        $this->io->writeError('<info>Generating extensions files</info>');
105
        $this->processPackage($this->composer->getPackage());
106
        foreach ($this->getPackages() as $package) {
107
            if ($package instanceof \Composer\Package\CompletePackageInterface) {
108
                $this->processPackage($package);
109
            }
110
        }
111
112
        foreach ($this->data as $name => $data) {
113
            $this->saveFile($this->buildOutputPath($name), $data);
114
        }
115
    }
116
117
    public function buildOutputPath($name)
118
    {
119
        return static::OUTPUT_PATH . DIRECTORY_SEPARATOR . $name . '.php';
120
    }
121
122
    /**
123
     * Writes file.
124
     * @param string $file
125
     * @param array $data
126
     */
127
    protected function saveFile($file, array $data)
128
    {
129
        $path = $this->getVendorDir() . '/' . $file;
130
        if (!file_exists(dirname($path))) {
131
            mkdir(dirname($path), 0777, true);
132
        }
133
        $array = str_replace("'" . self::BASE_DIR_SAMPLE, '$baseDir . \'', var_export($data, true));
134
        file_put_contents($path, "<?php\n\n\$baseDir = dirname(dirname(__DIR__));\n\nreturn $array;\n");
135
    }
136
137
    /**
138
     * Scans the given package and collects extensions data.
139
     * @param PackageInterface $package
140
     */
141
    public function processPackage(PackageInterface $package)
142
    {
143
        $extra = $package->getExtra();
144
        $files = isset($extra[self::EXTRA_OPTION_NAME]) ? $extra[self::EXTRA_OPTION_NAME] : null;
145
        if ($package->getType() !== self::PACKAGE_TYPE && is_null($files)) {
146
            return;
147
        }
148
149
        $extension = [
150
            'name'    => $package->getName(),
151
            'version' => $package->getVersion(),
152
        ];
153
        if ($package->getVersion() === '9999999-dev') {
154
            $reference = $package->getSourceReference() ?: $package->getDistReference();
155
            if ($reference) {
156
                $extension['reference'] = $reference;
157
            }
158
        }
159
        $this->data['extensions'][$package->getName()] = $extension;
160
161
        foreach ((array)$files as $name => $path) {
162
            $config = $this->readExtensionConfig($package, $path);
163
            $config['aliases'] = array_merge(
164
                isset($config['aliases']) ? (array)$config['aliases'] : [],
165
                $this->prepareAliases($package, 'psr-0'),
166
                $this->prepareAliases($package, 'psr-4')
167
            );
168
            $this->data[$name] = isset($this->data[$name]) ? static::mergeConfig($this->data[$name], $config) : $config;
169
        }
170
    }
171
172
    /**
173
     * Merges two or more arrays into one recursively.
174
     * Based on Yii2 yii\helpers\BaseArrayHelper::merge.
175
     * @param array $a array to be merged to
176
     * @param array $b array to be merged from
177
     * @return array the merged array
178
     */
179
    public static function mergeConfig($a, $b)
0 ignored issues
show
Unused Code introduced by
The parameter $a 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...
Unused Code introduced by
The parameter $b 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...
180
    {
181
        $args = func_get_args();
182
        $res = array_shift($args);
183
        foreach ($args as $items) {
184
            if (!is_array($items)) {
185
                continue;
186
            }
187
            foreach ($items as $k => $v) {
188
                if (is_int($k)) {
189
                    if (isset($res[$k])) {
190
                        $res[] = $v;
191
                    } else {
192
                        $res[$k] = $v;
193
                    }
194
                } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
195
                    $res[$k] = self::mergeConfig($res[$k], $v);
196
                } else {
197
                    $res[$k] = $v;
198
                }
199
            }
200
        }
201
202
        return $res;
203
    }
204
205
    /**
206
     * Read extra config.
207
     * @param string $file
208
     * @return array
209
     */
210
    protected function readExtensionConfig(PackageInterface $package, $file)
211
    {
212
        $path = $this->preparePath($package, $file);
213
        if (!file_exists($path)) {
214
            $this->io->writeError('<error>Non existent extension config file</error> ' . $file . ' in ' . $package->getName());
215
            exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method readExtensionConfig() 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...
216
        }
217
        return require $path;
218
    }
219
220
    /**
221
     * Prepare aliases.
222
     *
223
     * @param PackageInterface $package
224
     * @param string 'psr-0' or 'psr-4'
225
     * @return array
226
     */
227
    protected function prepareAliases(PackageInterface $package, $psr)
228
    {
229
        $autoload = $package->getAutoload();
230
        if (empty($autoload[$psr])) {
231
            return [];
232
        }
233
234
        $aliases = [];
235
        foreach ($autoload[$psr] as $name => $path) {
236
            if (is_array($path)) {
237
                // ignore psr-4 autoload specifications with multiple search paths
238
                // we can not convert them into aliases as they are ambiguous
239
                continue;
240
            }
241
            $name = str_replace('\\', '/', trim($name, '\\'));
242
            $path = $this->preparePath($package, $path);
243
            $path = $this->substitutePath($path, $this->getBaseDir(), self::BASE_DIR_SAMPLE);
244
            if ('psr-0' === $psr) {
245
                $path .= '/' . $name;
246
            }
247
            $aliases["@$name"] = $path;
248
        }
249
250
        return $aliases;
251
    }
252
253
    /**
254
     * Substitute path with alias if applicable.
255
     * @param string $path
256
     * @param string $dir
257
     * @param string $alias
258
     * @return string
259
     */
260
    public function substitutePath($path, $dir, $alias)
261
    {
262
        return (substr($path, 0, strlen($dir) + 1) === $dir . '/') ? $alias . substr($path, strlen($dir)) : $path;
263
    }
264
265
    public function preparePath(PackageInterface $package, $path)
266
    {
267
        if (!$this->getFilesystem()->isAbsolutePath($path)) {
268
            $prefix = $package instanceof RootPackageInterface ? $this->getBaseDir() : $this->getVendorDir() . '/' . $package->getPrettyName();
269
            $path = $prefix . '/' . $path;
270
        }
271
272
        return $this->getFilesystem()->normalizePath($path);
273
    }
274
275
    /**
276
     * Sets [[packages]].
277
     * @param PackageInterface[] $packages
278
     */
279 2
    public function setPackages(array $packages)
280
    {
281 2
        $this->packages = $packages;
282 2
    }
283
284
    /**
285
     * Gets [[packages]].
286
     * @return \Composer\Package\PackageInterface[]
287
     */
288 1
    public function getPackages()
289
    {
290 1
        if ($this->packages === null) {
291
            $this->packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
292
        }
293
294 1
        return $this->packages;
295
    }
296
297
    /**
298
     * Get absolute path to package base dir.
299
     * @return string
300
     */
301
    public function getBaseDir()
302
    {
303
        if ($this->baseDir === null) {
304
            $this->baseDir = dirname($this->getVendorDir());
305
        }
306
307
        return $this->baseDir;
308
    }
309
310
    /**
311
     * Get absolute path to composer vendor dir.
312
     * @return string
313
     */
314
    public function getVendorDir()
315
    {
316
        if ($this->vendorDir === null) {
317
            $dir = $this->composer->getConfig()->get('vendor-dir', '/');
318
            $this->vendorDir = $this->getFilesystem()->normalizePath($dir);
319
        }
320
321
        return $this->vendorDir;
322
    }
323
324
    /**
325
     * Getter for filesystem utility.
326
     * @return Filesystem
327
     */
328
    public function getFilesystem()
329
    {
330
        if ($this->filesystem === null) {
331
            $this->filesystem = new Filesystem();
332
        }
333
334
        return $this->filesystem;
335
    }
336
}
337