Completed
Branch master (7a7471)
by ANTHONIUS
05:27
created

AssetsInstaller::renderInstallOutput()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.2

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 6
nop 3
dl 0
loc 18
ccs 8
cts 10
cp 0.8
crap 5.2
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Yawik project.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
11
namespace Yawik\Composer;
12
13
use Composer\EventDispatcher\EventSubscriberInterface;
14
use Core\Application;
15
use Symfony\Component\Filesystem\Filesystem;
16
use Symfony\Component\Console\Style\SymfonyStyle;
17
use Symfony\Component\Finder\Finder;
18
use Yawik\Composer\Event\ConfigureEvent;
19
20
/**
21
 * Class AssetsInstaller
22
 * @package Yawik\Composer
23
 * @author  Anthonius Munthi <[email protected]>
24
 * @TODO    Create more documentation for methods
25
 */
26
class AssetsInstaller implements EventSubscriberInterface
27
{
28
    use LogTrait;
29
30
    const METHOD_COPY               = 'copy';
31
    const METHOD_ABSOLUTE_SYMLINK   = 'absolute symlink';
32
    const METHOD_RELATIVE_SYMLINK   = 'relative symlink';
33
34
    /**
35
     * @var Filesystem
36
     */
37
    protected $filesystem;
38
39
    public function __construct()
40
    {
41
        $this->filesystem = new Filesystem();
42
    }
43
44
    public static function getSubscribedEvents()
45
    {
46
        return [
47
            Plugin::YAWIK_ACTIVATE_EVENT => 'onActivateEvent',
48
            Plugin::YAWIK_CONFIGURE_EVENT => 'onConfigureEvent'
49
        ];
50
    }
51
52
    public function onConfigureEvent(ConfigureEvent $event)
53
    {
54
        $moduleAssets   = array();
55
        $modules        = $event->getModules();
56
57
        foreach ($modules as $module) {
58
            $className = get_class($module);
59
            $moduleName = substr($className, 0, strpos($className, '\\'));
60
            $r = new \ReflectionObject($module);
61
            $file = $r->getFileName();
62
            $dir = null;
63
            if ($module instanceof AssetProviderInterface) {
64
                $dir = $module->getPublicDir();
65
            } else {
66
                $testDir = substr($file, 0, stripos($file, 'src'.DIRECTORY_SEPARATOR.'Module.php')).'/public';
67
                if (is_dir($testDir)) {
68 7
                    $dir = $testDir;
69
                }
70 7
            }
71 7
            if (is_dir($dir)) {
72
                $moduleAssets[$moduleName] = realpath($dir);
73
            }
74
        }
75
        $this->install($moduleAssets);
76
    }
77
78 7
    /**
79 7
     * Install modules assets with the given $modules.
80
     * $modules should within this format:
81
     *
82
     * [module_name] => module_public_directory
83
     *
84
     * @param array     $modules An array of modules
85
     * @param string    $expectedMethod Expected install method
86
     */
87
    public function install($modules, $expectedMethod = null)
88
    {
89
        $publicDir      = $this->getModuleAssetDir();
90
        $rows           = [];
91
        $exitCode       = 0;
92 1
        $copyUsed       = false;
93
        $expectedMethod = is_null($expectedMethod) ? self::METHOD_RELATIVE_SYMLINK:$expectedMethod;
94 1
95 1
        foreach ($modules as $name => $originDir) {
96
            $targetDir = $publicDir.DIRECTORY_SEPARATOR.$name;
97
            $message = $name;
98
            try {
99
                $this->filesystem->remove($targetDir);
100
                if (self::METHOD_RELATIVE_SYMLINK == $expectedMethod) {
101
                    $method = $this->relativeSymlinkWithFallback($originDir, $targetDir);
102
                } elseif (self::METHOD_ABSOLUTE_SYMLINK == $expectedMethod) {
103
                    $expectedMethod = self::METHOD_ABSOLUTE_SYMLINK;
104
                    $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir);
105
                } else {
106
                    $expectedMethod = self::METHOD_COPY;
107
                    $method = $this->hardCopy($originDir, $targetDir);
108
                }
109 7
110
                if (self::METHOD_COPY === $method) {
111 7
                    $copyUsed = true;
112 7
                }
113
114
                if ($method === $expectedMethod) {
115
                    $rows[] = array(sprintf('<fg=green;options=bold>%s</>', '\\' === DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */), $message, $method);
116
                } else {
117
                    $rows[] = array(sprintf('<fg=yellow;options=bold>%s</>', '\\' === DIRECTORY_SEPARATOR ? 'WARNING' : '!'), $message, $method);
118
                }
119 7
            } catch (\Exception $e) {
120
                $exitCode = 1;
121 7
                $rows[] = array(sprintf('<fg=red;options=bold>%s</>', '\\' === DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */), $message, $e->getMessage());
122 7
            }
123
        }
124
125
        // render this output only on cli environment
126
        if ($this->isCli()) {
127
            $this->renderInstallOutput($copyUsed, $rows, $exitCode);
128
        }
129
    }
130
131
    /**
132
     * Uninstall modules
133
     *
134 5
     * @param array $modules A list of modules to uninstall
135
     */
136 5
    public function uninstall($modules)
137 5
    {
138 5
        $assetDir = $this->getModuleAssetDir();
139 5
        foreach ($modules as $name) {
140 5
            $publicPath = $assetDir.DIRECTORY_SEPARATOR.$name;
141 5
            if (is_dir($publicPath) || is_link($publicPath)) {
142
                $this->filesystem->remove($publicPath);
143 5
                $this->log("Removed module assets: <info>${name}</info>");
144 5
            }
145 5
        }
146
    }
147 5
148 5
    public function getPublicDir()
149 2
    {
150 3
        return $this->getRootDir().'/public';
151 2
    }
152 2
153
    public function getModuleAssetDir()
154 1
    {
155 1
        return $this->getPublicDir().'/modules';
156
    }
157
158 5
    /**
159 1
     * @return string
160
     */
161
    public function getRootDir()
162 5
    {
163 5
        return dirname(realpath(Application::getConfigDir()));
164
    }
165 5
166
    public function renderInstallOutput($copyUsed, $rows, $exitCode)
167
    {
168
        $io = new SymfonyStyle($this->getInput(), $this->getOutput());
169 5
        $io->newLine();
170
171
        $io->section('Yawik Assets Installed!');
172
173
        if ($rows) {
174 5
            $io->table(array('', 'Module', 'Method / Error'), $rows);
175 5
        }
176
177 5
        if (0 !== $exitCode) {
178
            $io->error('Some errors occurred while installing assets.');
179 1
        } else {
180
            if ($copyUsed) {
181 1
                $io->note('Some assets were installed via copy. If you make changes to these assets you have to run this command again.');
182 1
            }
183 1
            $io->success($rows ? 'All assets were successfully installed.' : 'No assets were provided by any bundle.');
184 1
        }
185 1
    }
186 1
187
    /**
188
     * Try to create absolute symlink.
189
     *
190
     * Falling back to hard copy.
191
     */
192
    public function absoluteSymlinkWithFallback($originDir, $targetDir)
193
    {
194
        try {
195
            $this->symlink($originDir, $targetDir);
196
            $method = self::METHOD_ABSOLUTE_SYMLINK;
197 1
        } catch (\Exception $e) {
198 1
            // fall back to copy
199 1
            $method = $this->hardCopy($originDir, $targetDir);
200
        }
201 1
        return $method;
202 1
    }
203 1
204
    /**
205 1
     * Try to create relative symlink.
206
     *
207
     * Falling back to absolute symlink and finally hard copy.
208
     */
209 1
    public function relativeSymlinkWithFallback($originDir, $targetDir)
210
    {
211
        try {
212
            $this->symlink($originDir, $targetDir, true);
213 1
            $method = self::METHOD_RELATIVE_SYMLINK;
214 1
        } catch (\Exception $e) {
215 1
            $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir);
216 1
        }
217 1
218
        return $method;
219
    }
220
221 6
    /**
222
     * Creates symbolic link.
223
     *
224
     * @throws \Exception if link can not be created
225
     */
226 6
    public function symlink($originDir, $targetDir, $relative = false)
227
    {
228
        if ($relative) {
229
            $this->filesystem->mkdir(dirname($targetDir));
230
            $originDir = $this->filesystem->makePathRelative($originDir, realpath(dirname($targetDir)));
231
        }
232
        $this->filesystem->symlink($originDir, $targetDir);
233
234 6
        if (!file_exists($targetDir)) {
235
            throw new \Exception(
236
                sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir),
237
                0,
238
                null
239
            );
240
        }
241
    }
242
243
    /**
244
     * Copies origin to target.
245
     */
246
    public function hardCopy($originDir, $targetDir)
247
    {
248
        $this->filesystem->mkdir($targetDir, 0777);
249
        // We use a custom iterator to ignore VCS files
250
        $this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir));
251
252
        return self::METHOD_COPY;
253
    }
254
}
255