Completed
Branch master (5a33e7)
by ANTHONIUS
02:09
created

AssetsInstaller::getModulesAsset()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5

Importance

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