Passed
Push — master ( 4527da...1acad2 )
by Daniel
01:17
created

AssetEngine::assetFiles()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 5

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 30
ccs 21
cts 21
cp 1
rs 9.2888
c 0
b 0
f 0
cc 5
nc 6
nop 2
crap 5
1
<?php
2
3
namespace Odan\Asset;
4
5
use JSMin\JSMin;
6
use RuntimeException;
7
use Symfony\Component\Cache\Adapter\AdapterInterface;
8
use Symfony\Component\Cache\Adapter\ArrayAdapter;
9
use tubalmartin\CssMin\Minifier as CssMinifier;
10
11
/**
12
 * Extension that adds the ability to cache and minify assets.
13
 */
14
final class AssetEngine
15
{
16
    /**
17
     * Cache.
18
     *
19
     * @var AdapterInterface
20
     */
21
    private $cache;
22
23
    /**
24
     * @var AssetCache
25
     */
26
    private $publicCache;
27
28
    /**
29
     * Enables minify.
30
     *
31
     * @var array
32
     */
33
    private $options = [
34
        'minify' => true,
35
        'inline' => true,
36
        'public_dir' => null,
37
        'name' => 'file',
38
    ];
39
40
    /**
41
     * Create new instance.
42
     *
43
     * @param array $options
44
     */
45 3
    public function __construct(array $options)
46
    {
47 3
        if (!empty($options['cache']) && $options['cache'] instanceof AdapterInterface) {
48 3
            $this->cache = $options['cache'];
49
        } else {
50
            $this->cache = new ArrayAdapter();
51
        }
52 3
        $this->publicCache = new AssetCache($options['public_dir']);
53
54 3
        unset($options['public_cache']);
55 3
        unset($options['cache']);
56
57 3
        $this->options = array_replace_recursive($this->options, $options);
58 3
    }
59
60
    /**
61
     * Render and compress JavaScript assets.
62
     *
63
     * @param string $asset
64
     * @param array $options
65
     *
66
     * @return string content
67
     */
68 2
    public function assetFile(string $asset, array $options = []): string
69
    {
70 2
        return $this->assetFiles((array)$asset, $options);
71
    }
72
73
    /**
74
     * Render and compress JavaScript assets.
75
     *
76
     * @param array $assets
77
     * @param array $options
78
     *
79
     * @return string content
80
     */
81 2
    public function assetFiles(array $assets, array $options = []): string
82
    {
83 2
        $assets = $this->prepareAssets($assets);
84 2
        $options = array_replace_recursive($this->options, $options);
85
86 2
        $cacheKey = $this->getCacheKey($assets, $options);
87 2
        $cacheItem = $this->cache->getItem($cacheKey);
88 2
        if ($cacheItem->isHit()) {
89 2
            return $cacheItem->get();
90
        }
91
92 2
        $jsFiles = [];
93 2
        $cssFiles = [];
94 2
        foreach ($assets as $file) {
95 2
            $fileType = strtolower(pathinfo($file, PATHINFO_EXTENSION));
96 2
            if ($fileType == 'js') {
97 2
                $jsFiles[] = $file;
98
            }
99 2
            if ($fileType == 'css') {
100 2
                $cssFiles[] = $file;
101
            }
102
        }
103 2
        $cssContent = $this->css($cssFiles, $options);
104 2
        $jsContent = $this->js($jsFiles, $options);
105 2
        $result = $cssContent . $jsContent;
106
107 2
        $cacheItem->set($result);
108 2
        $this->cache->save($cacheItem);
109
110 2
        return $result;
111
    }
112
113
    /**
114
     * Resolve real asset filenames.
115
     *
116
     * @param array $assets
117
     *
118
     * @return array
119
     */
120 2
    protected function prepareAssets(array $assets): array
121
    {
122 2
        $result = [];
123 2
        foreach ($assets as $name) {
124 2
            $result[] = $this->getRealFilename($name);
125
        }
126
127 2
        return $result;
128
    }
129
130
    /**
131
     * Render and compress CSS assets.
132
     *
133
     * @param array $assets
134
     * @param array $options
135
     *
136
     * @return string content
137
     */
138 2
    public function js(array $assets, array $options): string
139
    {
140 2
        $contents = [];
141 2
        $public = '';
142 2
        foreach ($assets as $asset) {
143 2
            if ($this->isExternalUrl($asset)) {
144
                // External url
145
                $contents[] = sprintf('<script src="%s"></script>', $asset);
146
                continue;
147
            }
148 2
            $content = $this->getJsContent($asset, $options['minify']);
149
150 2
            if (!empty($options['inline'])) {
151 1
                $contents[] = sprintf('<script>%s</script>', $content);
152
            } else {
153 2
                $public .= $content . '';
154
            }
155
        }
156 2
        if (strlen($public) > 0) {
157 1
            $name = isset($options['name']) ? $options['name'] : 'file.js';
158 1
            if (empty(pathinfo($name, PATHINFO_EXTENSION))) {
159 1
                $name .= '.js';
160
            }
161 1
            $url = $this->publicCache->createCacheBustedUrl($name, $public);
162 1
            $contents[] = sprintf('<script src="%s"></script>', $url);
163
        }
164 2
        $result = implode("\n", $contents);
165
166 2
        return $result;
167
    }
168
169
    /**
170
     * Minimise JS.
171
     *
172
     * @param string $file Name of default JS file
173
     * @param bool $minify Minify js if true
174
     *
175
     * @throws RuntimeException
176
     *
177
     * @return string JavaScript code
178
     */
179 2
    protected function getJsContent(string $file, bool $minify): string
180
    {
181 2
        $content = file_get_contents($file);
182
183 2
        if ($content === false) {
184
            throw new RuntimeException(sprintf('File could be read: %s', $file));
185
        }
186
187 2
        if ($minify) {
188 2
            $content = JsMin::minify($content);
189
        }
190
191 2
        return $content;
192
    }
193
194
    /**
195
     * Render and compress CSS assets.
196
     *
197
     * @param array $assets
198
     * @param array $options
199
     *
200
     * @return string content
201
     */
202 2
    public function css(array $assets, array $options)
203
    {
204 2
        $contents = [];
205 2
        $public = '';
206
207 2
        foreach ($assets as $asset) {
208
            if ($this->isExternalUrl($asset)) {
209
                // External url
210
                $contents[] = sprintf('<link rel="stylesheet" type="text/css" href="%s" media="all" />', $asset);
211
                continue;
212
            }
213
            $content = $this->getCssContent($asset, $options['minify']);
214
215
            if (!empty($options['inline'])) {
216
                $contents[] = sprintf('<style>%s</style>', $content);
217
            } else {
218
                $public .= $content . '';
219
            }
220
        }
221
222 2
        if (strlen($public) > 0) {
223
            $name = isset($options['name']) ? $options['name'] : 'file.css';
224
            if (empty(pathinfo($name, PATHINFO_EXTENSION))) {
225
                $name .= '.css';
226
            }
227
            $url = $this->publicCache->createCacheBustedUrl($name, $public);
228
            $contents[] = sprintf('<link rel="stylesheet" type="text/css" href="%s" media="all" />', $url);
229
        }
230 2
        $result = implode("\n", $contents);
231
232 2
        return $result;
233
    }
234
235
    /**
236
     * Minimize CSS.
237
     *
238
     * @param string $fileName Name of default CSS file
239
     * @param bool $minify Minify css if true
240
     *
241
     * @throws RuntimeException
242
     *
243
     * @return string CSS code
244
     */
245
    public function getCssContent(string $fileName, bool $minify): string
246
    {
247
        $content = file_get_contents($fileName);
248
249
        if ($content === false) {
250
            throw new RuntimeException(sprintf('File could be read: %s', $fileName));
251
        }
252
253
        if ($minify === true) {
254
            $compressor = new CssMinifier();
255
            $content = $compressor->run($content);
256
        }
257
258
        return $content;
259
    }
260
261
    /**
262
     * Get cache key.
263
     *
264
     * @param array $assets
265
     * @param array $settings
266
     *
267
     * @return string
268
     */
269 2
    protected function getCacheKey(array $assets, array $settings = null): string
270
    {
271 2
        $keys = [];
272 2
        foreach ($assets as $file) {
273 2
            $keys[] = sha1_file($file);
274
        }
275 2
        $keys[] = sha1(serialize($settings));
276
277 2
        return sha1(implode('', $keys));
278
    }
279
280
    /**
281
     * Check if url is valid.
282
     *
283
     * @param string $url
284
     *
285
     * @return bool
286
     */
287 2
    protected function isExternalUrl(string $url): bool
288
    {
289 2
        return (!filter_var($url, FILTER_VALIDATE_URL) === false) && (strpos($url, 'vfs://') === false);
290
    }
291
292
    /**
293
     * Returns full path and filename.
294
     *
295
     * @param string $filename
296
     *
297
     * @return string
298
     */
299 2
    protected function getRealFilename(string $filename): string
300
    {
301 2
        return $filename;
302
    }
303
}
304