Completed
Push — master ( 62b7c8...249848 )
by Anton
08:38
created

Plugin::removeExtras()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
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\PackageEvent;
17
use Composer\Installer\PackageEvents;
18
use Composer\IO\IOInterface;
19
use Composer\Plugin\PluginInterface;
20
use Symfony\Component\Filesystem\Exception\IOException;
21
use Symfony\Component\Filesystem\Filesystem;
22
use Symfony\Component\Finder\Finder;
23
24
/**
25
 * Class Plugin
26
 *
27
 * @package Bluz\Composer\Installers
28
 */
29
class Plugin implements PluginInterface, EventSubscriberInterface
30
{
31
    const PERMISSION_CODE = 0755;
32
    const REPEAT = 5;
33
    CONST DIRECTORIES = [
34
        'application',
35
        'data',
36
        'public',
37
        'tests'
38
    ];
39
40
    /**
41
     * @var Installer
42
     */
43
    protected $installer;
44
45
    /**
46
     * @var string
47
     */
48
    protected $environment;
49
50
    /**
51
     * @var Filesystem
52
     */
53
    protected $filesystem;
54
55
    /**
56
     * Create instance, define constants
57
     */
58
    public function __construct()
59
    {
60
        defined('PATH_ROOT') ? : define('PATH_ROOT', realpath($_SERVER['DOCUMENT_ROOT']));
61
        defined('DS') ? : define('DS', DIRECTORY_SEPARATOR);
62
    }
63
64
    /**
65
     * Called after the plugin is loaded
66
     *
67
     * It setup composer installer
68
     *
69
     * {@inheritDoc}
70
     */
71
    public function activate(Composer $composer, IOInterface $io)
72
    {
73
        $this->installer = new Installer($io, $composer);
74
        $composer->getInstallationManager()->addInstaller($this->installer);
75
    }
76
77
    /**
78
     * Registered events after the plugin is loaded
79
     *
80
     * {@inheritDoc}
81
     */
82
    public static function getSubscribedEvents(): array
83
    {
84
        return [
85
            // copy files to working directory
86
            PackageEvents::POST_PACKAGE_INSTALL => 'copyFiles',
87
            // removed unchanged files
88
            PackageEvents::PRE_PACKAGE_UPDATE => 'removeFiles',
89
            // copy new files
90
            PackageEvents::POST_PACKAGE_UPDATE => 'copyFiles',
91
            // removed all files
92
            PackageEvents::PRE_PACKAGE_UNINSTALL => 'removeFiles',
93
        ];
94
    }
95
96
    /**
97
     * Hook which is called after install package
98
     *
99
     * It copies bluz module
100
     *
101
     * @throws \InvalidArgumentException
102
     */
103 View Code Duplication
    public function copyFiles(PackageEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
104
    {
105
        if (file_exists($this->installer->getVendorPath())) {
106
            $this->copyModule();
107
        }
108
109
        $extras = $event->getComposer()->getPackage()->getExtra();
110
        if (array_key_exists('copy-files', $extras)) {
111
            $this->copyExtras($extras['copy-files']);
112
        }
113
    }
114
115
    /**
116
     * Hook which is called before update package
117
     *
118
     * It checks bluz module
119
     */
120 View Code Duplication
    public function removeFiles(PackageEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
121
    {
122
        if (file_exists($this->installer->getVendorPath())) {
123
            $this->removeModule();
124
        }
125
126
        $extras = $event->getComposer()->getPackage()->getExtra();
127
        if (array_key_exists('copy-files', $extras)) {
128
            $this->removeExtras($extras['copy-files']);
129
        }
130
    }
131
132
    /**
133
     * Get Filesystem
134
     * 
135
     * @return Filesystem
136
     */
137
    protected function getFilesystem()
138
    {
139
        if (!$this->filesystem) {
140
            $this->filesystem = new Filesystem();
141
        }
142
        return $this->filesystem;
143
    }
144
145
    /**
146
     * getExtra
147
     *
148
     * @return array
149
     */
150
    protected function getExtraFiles() : array
151
    {
152
        $moduleJson = json_decode(file_get_contents($this->installer->getVendorPath() .DS. 'composer.json'), true);
153
154
        if (isset($moduleJson, $moduleJson['extra'], $moduleJson['extra']['copy-files'])) {
155
            return $moduleJson['extra']['copy-files'];
156
        }
157
        return [];
158
    }
159
160
    /**
161
     * Copy Module files
162
     *
163
     * @return void
164
     * @throws \InvalidArgumentException
165
     */
166
    protected function copyModule()
167
    {
168
        $this->copyExtras($this->getExtraFiles());
169
170
        foreach (self::DIRECTORIES as $directory) {
171
            $this->copy(
172
                $this->installer->getVendorPath() .DS. $directory . DS,
173
                PATH_ROOT . DS . $directory . DS
174
            );
175
        }
176
177
        $this->installer->getIo()->write(
178
            sprintf('  - Copied <comment>%s</comment> module to application', basename($this->installer->getVendorPath())),
179
            true
180
        );
181
    }
182
183
    /**
184
     * copyExtras
185
     *
186
     * @param  array $files
187
     *
188
     * @return void
189
     * @throws \InvalidArgumentException
190
     */
191
    protected function copyExtras($files)
192
    {
193
        foreach ($files as $source => $target) {
194
            $this->copy(
195
                dirname($this->installer->getVendorPath(), 2) .DS. $source,
196
                PATH_ROOT . DS . $target
197
            );
198
        }
199
    }
200
201
    /**
202
     * It recursively copies the files and directories
203
     *
204
     * @param $source
205
     * @param $target
206
     *
207
     * @return void
208
     * @throws \InvalidArgumentException
209
     */
210
    protected function copy($source, $target)
211
    {
212
        // skip, if not exists
213
        if (!file_exists($source)) {
214
            return;
215
        }
216
217
        // Check the renaming of file for direct moving (file-to-file)
218
        $isRenameFile = substr($target, -1) !== '/' && !is_dir($source);
219
220
        if (file_exists($target) && !is_dir($target) && !$isRenameFile) {
221
            throw new \InvalidArgumentException('Destination directory is not a directory');
222
        }
223
224
        try {
225
            if ($isRenameFile) {
226
                $this->getFilesystem()->mkdir(dirname($target));
227
            } else {
228
                $this->getFilesystem()->mkdir($target);
229
            }
230
        } catch (IOException $e) {
231
            throw new \InvalidArgumentException(
232
                sprintf('Could not create directory `%s`', $target)
233
            );
234
        }
235
236
        if (false === file_exists($source)) {
237
            throw new \InvalidArgumentException(
238
                sprintf('Source directory or file `%s` does not exist', $source)
239
            );
240
        }
241
242
        if (is_dir($source)) {
243
            $finder = new Finder;
244
            $finder->files()->in($source);
245
246
            foreach ($finder as $file) {
247
                try {
248
                    $this->getFilesystem()->copy($file, $target .DS. $file->getRelativePathname());
249
                } catch (IOException $e) {
250
                    throw new \InvalidArgumentException(
251
                        sprintf('Could not copy `%s`', $file->getBaseName())
252
                    );
253
                }
254
            }
255
        } else {
256
            try {
257
                if ($isRenameFile) {
258
                    $this->getFilesystem()->copy($source, $target);
259
                } else {
260
                    $this->getFilesystem()->copy($source, $target.'/'.basename($source));
261
                }
262
            } catch (IOException $e) {
263
                throw new \InvalidArgumentException(sprintf('Could not copy `%s`', $source));
264
            }
265
        }
266
267
        $this->installer->getIo()->write(
268
            sprintf('  - Copied file(s) from <comment>%s</comment> to <comment>%s</comment>', $source, $target),
269
            true,
270
            IOInterface::VERBOSE
271
        );
272
    }
273
274
    /**
275
     * It recursively removes the files and empty directories
276
     * @return void
277
     */
278
    protected function removeModule()
279
    {
280
        $this->removeExtras($this->getExtraFiles());
281
282
        foreach (self::DIRECTORIES as $directory) {
283
            $this->remove($directory);
284
        }
285
286
        $this->installer->getIo()->write(
287
            sprintf('  - Removed <comment>%s</comment> module from application', basename($this->installer->getVendorPath())),
288
            true
289
        );
290
    }
291
292
    /**
293
     * removeExtras
294
     *
295
     * @return void
296
     */
297
    protected function removeExtras($files)
298
    {
299
        foreach ($files as $source => $target) {
300
            $this->remove(PATH_ROOT . DS . $target);
301
        }
302
    }
303
304
    /**
305
     * It recursively removes the files and directories
306
     * @param $directory
307
     * @return void
308
     */
309
    protected function remove($directory)
310
    {
311
        $sourcePath = $this->installer->getVendorPath() . DS . $directory;
312
313
        if (!is_dir($sourcePath)) {
314
            return;
315
        }
316
        foreach ($iterator = new \RecursiveIteratorIterator(
317
            new \RecursiveDirectoryIterator(
318
                $sourcePath,
319
                \RecursiveDirectoryIterator::SKIP_DOTS
320
            ),
321
            \RecursiveIteratorIterator::CHILD_FIRST
322
        ) as $item) {
323
            // path to copied file
324
            $current = PATH_ROOT . DS . $directory . DS . $iterator->getSubPathName();
325
326
            // remove empty directories
327 View Code Duplication
            if (is_dir($current)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
328
                if (count(scandir($current, SCANDIR_SORT_ASCENDING )) === 2) {
329
                    rmdir($current);
330
                    $this->installer->getIo()->write(
331
                        "  - Removed directory `{$iterator->getSubPathName()}`",
332
                        true,
333
                        IOInterface::VERBOSE
334
                    );
335
                } else {
336
                    $this->installer->getIo()->write(
337
                        "  - <comment>Skip directory `{$iterator->getSubPathName()}`</comment>",
338
                        true,
339
                        IOInterface::VERBOSE
340
                    );
341
                }
342
                continue;
343
            }
344
345
            // skip already removed files
346
            if (!is_file($current)) {
347
                continue;
348
            }
349
350 View Code Duplication
            if (md5_file($item) === md5_file($current)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
351
                // remove file
352
                unlink($current);
353
                $this->installer->getIo()->write(
354
                    "  - Removed file `{$iterator->getSubPathName()}`",
355
                    true,
356
                    IOInterface::VERBOSE
357
                );
358
            } else {
359
                // or skip changed files
360
                $this->installer->getIo()->write(
361
                    "  - <comment>File `{$iterator->getSubPathName()}` has changed</comment>",
362
                    true,
363
                    IOInterface::VERBOSE
364
                );
365
            }
366
        }
367
    }
368
}
369