Completed
Push — master ( 0406ae...0cb760 )
by ANTHONIUS
02:12
created

AssetsInstaller::setFilesystem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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