AssetsInstaller::relativeSymlinkWithFallback()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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