Completed
Push — master ( 5cbe7c...bef131 )
by Anton
12s
created

Plugin   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 372
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 45
lcom 1
cbo 8
dl 0
loc 372
rs 8.3673
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 3
A activate() 0 5 1
A getSubscribedEvents() 0 14 1
A copyProjectExtraFiles() 0 11 2
A copyModuleFiles() 0 6 2
A removeModuleFiles() 0 6 2
A getFilesystem() 0 7 2
A getExtraFiles() 0 9 2
A copyModule() 0 19 2
A copyExtras() 0 9 2
C copy() 0 72 15
A removeModule() 0 16 2
A removeExtras() 0 9 2
B remove() 0 62 7

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
 * Bluz composer plugin
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link https://github.com/bluzphp/composer-plugin
7
 */
8
9
/**
10
 * @namespace
11
 */
12
namespace Bluz\Composer\Installers;
13
14
use Composer\Composer;
15
use Composer\EventDispatcher\EventSubscriberInterface;
16
use Composer\Installer\PackageEvents;
17
use Composer\IO\IOInterface;
18
use Composer\Plugin\PluginInterface;
19
use Composer\Script\Event;
20
use Composer\Script\ScriptEvents;
21
use Symfony\Component\Filesystem\Exception\IOException;
22
use Symfony\Component\Filesystem\Filesystem;
23
use Symfony\Component\Finder\Finder;
24
25
/**
26
 * Class Plugin
27
 *
28
 * @package Bluz\Composer\Installers
29
 */
30
class Plugin implements PluginInterface, EventSubscriberInterface
31
{
32
    const PERMISSION_CODE = 0755;
33
    const REPEAT = 5;
34
    const DIRECTORIES = [
35
        'application',
36
        'data',
37
        'public',
38
        'tests'
39
    ];
40
41
    /**
42
     * @var Installer
43
     */
44
    protected $installer;
45
46
    /**
47
     * @var string
48
     */
49
    protected $environment;
50
51
    /**
52
     * @var Filesystem
53
     */
54
    protected $filesystem;
55
56
    /**
57
     * Create instance, define constants
58
     */
59
    public function __construct()
60
    {
61
        defined('PATH_ROOT') ?: define('PATH_ROOT', realpath($_SERVER['DOCUMENT_ROOT']));
62
        defined('DS') ?: define('DS', DIRECTORY_SEPARATOR);
63
    }
64
65
    /**
66
     * Called after the plugin is loaded
67
     *
68
     * It setup composer installer
69
     *
70
     * {@inheritDoc}
71
     */
72
    public function activate(Composer $composer, IOInterface $io)
73
    {
74
        $this->installer = new Installer($io, $composer);
75
        $composer->getInstallationManager()->addInstaller($this->installer);
76
    }
77
78
    /**
79
     * Registered events after the plugin is loaded
80
     *
81
     * {@inheritDoc}
82
     */
83
    public static function getSubscribedEvents(): array
84
    {
85
        return [
86
            // copy extra files from root composer.json
87
            // do it only once after create project
88
            ScriptEvents::POST_ROOT_PACKAGE_INSTALL => 'copyProjectExtraFiles',
89
            // copy module's files to working directory
90
            PackageEvents::POST_PACKAGE_INSTALL => 'copyModuleFiles',
91
            PackageEvents::POST_PACKAGE_UPDATE => 'copyModuleFiles',
92
            // removed unchanged module's files
93
            PackageEvents::PRE_PACKAGE_UPDATE => 'removeModuleFiles',
94
            PackageEvents::PRE_PACKAGE_UNINSTALL => 'removeModuleFiles',
95
        ];
96
    }
97
98
    /**
99
     * Copy extra files from compose.json of project
100
     *
101
     * @param Event $event
102
     *
103
     * @return void
104
     * @throws \InvalidArgumentException
105
     */
106
    public function copyProjectExtraFiles(Event $event)
107
    {
108
        $extras = $event->getComposer()->getPackage()->getExtra();
109
        if (array_key_exists('copy-files', $extras)) {
110
            $this->installer->getIo()->write(
111
                sprintf('  - Copied additional file(s)'),
112
                true
113
            );
114
            $this->copyExtras($extras['copy-files']);
115
        }
116
    }
117
118
    /**
119
     * Hook which is called after install package
120
     * It copies bluz module
121
     *
122
     * @throws \InvalidArgumentException
123
     */
124
    public function copyModuleFiles()
125
    {
126
        if (file_exists($this->installer->getVendorPath())) {
127
            $this->copyModule();
128
        }
129
    }
130
131
    /**
132
     * Hook which is called before update package
133
     * It checks bluz module
134
     */
135
    public function removeModuleFiles()
136
    {
137
        if (file_exists($this->installer->getVendorPath())) {
138
            $this->removeModule();
139
        }
140
    }
141
142
    /**
143
     * Get Filesystem
144
     *
145
     * @return Filesystem
146
     */
147
    protected function getFilesystem()
148
    {
149
        if (!$this->filesystem) {
150
            $this->filesystem = new Filesystem();
151
        }
152
        return $this->filesystem;
153
    }
154
155
    /**
156
     * getExtra
157
     *
158
     * @return array
159
     */
160
    protected function getExtraFiles() : array
161
    {
162
        $moduleJson = json_decode(file_get_contents($this->installer->getVendorPath() . DS . 'composer.json'), true);
163
164
        if (isset($moduleJson, $moduleJson['extra'], $moduleJson['extra']['copy-files'])) {
165
            return $moduleJson['extra']['copy-files'];
166
        }
167
        return [];
168
    }
169
170
    /**
171
     * Copy Module files
172
     *
173
     * @return void
174
     * @throws \InvalidArgumentException
175
     */
176
    protected function copyModule()
177
    {
178
        $this->copyExtras($this->getExtraFiles());
179
180
        foreach (self::DIRECTORIES as $directory) {
181
            $this->copy(
182
                $this->installer->getVendorPath() . DS . $directory . DS,
183
                PATH_ROOT . DS . $directory . DS
184
            );
185
        }
186
187
        $this->installer->getIo()->write(
188
            sprintf(
189
                '  - Copied <comment>%s</comment> module to application',
190
                basename($this->installer->getVendorPath())
191
            ),
192
            true
193
        );
194
    }
195
196
    /**
197
     * copyExtras
198
     *
199
     * @param  array $files
200
     *
201
     * @return void
202
     * @throws \InvalidArgumentException
203
     */
204
    protected function copyExtras($files)
205
    {
206
        foreach ($files as $source => $target) {
207
            $this->copy(
208
                dirname($this->installer->getVendorPath(), 2) . DS . $source,
209
                PATH_ROOT . DS . $target
210
            );
211
        }
212
    }
213
214
    /**
215
     * It recursively copies the files and directories
216
     *
217
     * @param $source
218
     * @param $target
219
     *
220
     * @return void
221
     * @throws \InvalidArgumentException
222
     */
223
    protected function copy($source, $target)
224
    {
225
        // skip, if not exists
226
        if (!file_exists($source)) {
227
            return;
228
        }
229
        // skip, if target exists
230
        if (is_file($target)) {
231
            $this->installer->getIo()->write(
232
                sprintf('  - File <comment>%s</comment> already exists', $target),
233
                true,
234
                IOInterface::VERBOSE
235
            );
236
            return;
237
        }
238
239
        // Check the renaming of file for direct moving (file-to-file)
240
        $isRenameFile = substr($target, -1) !== '/' && !is_dir($source);
241
242
        if (file_exists($target) && !is_dir($target) && !$isRenameFile) {
243
            throw new \InvalidArgumentException('Destination directory is not a directory');
244
        }
245
246
        try {
247
            if ($isRenameFile) {
248
                $this->getFilesystem()->mkdir(dirname($target));
249
            } else {
250
                $this->getFilesystem()->mkdir($target);
251
            }
252
        } catch (IOException $e) {
253
            throw new \InvalidArgumentException(
254
                sprintf('Could not create directory `%s`', $target)
255
            );
256
        }
257
258
        if (false === file_exists($source)) {
259
            throw new \InvalidArgumentException(
260
                sprintf('Source directory or file `%s` does not exist', $source)
261
            );
262
        }
263
264
        if (is_dir($source)) {
265
            $finder = new Finder;
266
            $finder->files()->in($source);
267
268
            foreach ($finder as $file) {
269
                try {
270
                    $this->getFilesystem()->copy($file, $target . DS . $file->getRelativePathname());
271
                } catch (IOException $e) {
272
                    throw new \InvalidArgumentException(
273
                        sprintf('Could not copy `%s`', $file->getBaseName())
274
                    );
275
                }
276
            }
277
        } else {
278
            try {
279
                if ($isRenameFile) {
280
                    $this->getFilesystem()->copy($source, $target);
281
                } else {
282
                    $this->getFilesystem()->copy($source, $target . '/' . basename($source));
283
                }
284
            } catch (IOException $e) {
285
                throw new \InvalidArgumentException(sprintf('Could not copy `%s`', $source));
286
            }
287
        }
288
289
        $this->installer->getIo()->write(
290
            sprintf('  - Copied file(s) from <comment>%s</comment> to <comment>%s</comment>', $source, $target),
291
            true,
292
            IOInterface::VERBOSE
293
        );
294
    }
295
296
    /**
297
     * It recursively removes the files and empty directories
298
     * @return void
299
     */
300
    protected function removeModule()
301
    {
302
        $this->removeExtras($this->getExtraFiles());
303
304
        foreach (self::DIRECTORIES as $directory) {
305
            $this->remove($directory);
306
        }
307
308
        $this->installer->getIo()->write(
309
            sprintf(
310
                '  - Removed <comment>%s</comment> module from application',
311
                basename($this->installer->getVendorPath())
312
            ),
313
            true
314
        );
315
    }
316
317
    /**
318
     * removeExtras
319
     *
320
     * @param  array $files
321
     *
322
     * @return void
323
     */
324
    protected function removeExtras($files)
325
    {
326
        foreach ($files as $source => $target) {
327
            $this->installer->getIo()->write(
328
                sprintf('  - Skipped additional file(s) <comment>%s</comment>', $target),
329
                true
330
            );
331
        }
332
    }
333
334
    /**
335
     * It recursively removes the files and directories
336
     * @param $directory
337
     * @return void
338
     */
339
    protected function remove($directory)
340
    {
341
        $sourcePath = $this->installer->getVendorPath() . DS . $directory;
342
343
        if (!is_dir($sourcePath)) {
344
            return;
345
        }
346
        foreach ($iterator = new \RecursiveIteratorIterator(
347
            new \RecursiveDirectoryIterator(
348
                $sourcePath,
349
                \RecursiveDirectoryIterator::SKIP_DOTS
350
            ),
351
            \RecursiveIteratorIterator::CHILD_FIRST
352
        ) as $item) {
353
            // path to copied file
354
            $current = PATH_ROOT . DS . $directory . DS . $iterator->getSubPathName();
355
356
            // remove empty directories
357
            if (is_dir($current)) {
358
                if (count(scandir($current, SCANDIR_SORT_ASCENDING)) === 2) {
359
                    rmdir($current);
360
                    $this->installer->getIo()->write(
361
                        "  - Removed directory `{$iterator->getSubPathName()}`",
362
                        true,
363
                        IOInterface::VERBOSE
364
                    );
365
                } else {
366
                    $this->installer->getIo()->write(
367
                        sprintf(
368
                            '  - <comment>Skipped directory `%s`</comment>',
369
                            $directory . DS . $iterator->getSubPathName()
370
                        ),
371
                        true,
372
                        IOInterface::VERBOSE
373
                    );
374
                }
375
                continue;
376
            }
377
378
            // skip already removed files
379
            if (!is_file($current)) {
380
                continue;
381
            }
382
383
            if (md5_file($item) === md5_file($current)) {
384
                // remove file
385
                unlink($current);
386
                $this->installer->getIo()->write(
387
                    "  - Removed file `{$iterator->getSubPathName()}`",
388
                    true,
389
                    IOInterface::VERBOSE
390
                );
391
            } else {
392
                // or skip changed files
393
                $this->installer->getIo()->write(
394
                    "  - <comment>File `{$iterator->getSubPathName()}` has changed</comment>",
395
                    true,
396
                    IOInterface::VERBOSE
397
                );
398
            }
399
        }
400
    }
401
}
402