Passed
Push — master ( a30112...8b9bad )
by Daniel
01:34
created

AssetEngine::js()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6.0493

Importance

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