Passed
Branch master (4933f5)
by ANTHONIUS
05:09
created

AssetsInstaller::hardCopy()   A

Complexity

Conditions 1
Paths 1

Size

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