Completed
Push — master ( e3fea5...0406ae )
by ANTHONIUS
02:21
created

AssetsInstaller::renderInstallOutput()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5.9256

Importance

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