Completed
Branch master (50a96e)
by ANTHONIUS
05:47
created

AssetsInstaller::getSubscribedEvents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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