Passed
Push — master ( b2cf4a...c48dc4 )
by Samuel
02:01
created

JsImportCacheBuster   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 66
Duplicated Lines 0 %

Test Coverage

Coverage 16%

Importance

Changes 0
Metric Value
wmc 10
eloc 26
dl 0
loc 66
ccs 4
cts 25
cp 0.16
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
B addVersionToJsImports() 0 43 9
A __construct() 0 5 1
1
<?php
2
3
namespace App\Core\Infrastructure\Utility;
4
5
use FilesystemIterator;
6
use RecursiveDirectoryIterator;
7
use RecursiveIteratorIterator;
8
9
/**
10
 * Adds version number to js imports to break cache on version change.
11
 */
12
final class JsImportCacheBuster
13
{
14
    private ?string $version;
15
    private string $assetPath;
16
17 17
    public function __construct(Settings $settings)
18
    {
19 17
        $deploymentSettings = $settings->get('deployment');
20 17
        $this->version = $deploymentSettings['version'];
21 17
        $this->assetPath = $deploymentSettings['asset_path'];
22
    }
23
24
    /**
25
     * All js files inside the given directory that contain ES6 imports
26
     * are modified so that the imports have the version number at the
27
     * end of the file name as query parameters to break cache on
28
     * version change.
29
     * This function is called in PhpRendererMiddleware only on dev env.
30
     * Performance wise, this function takes between 10 and 20ms when content
31
     * is unchanged and between 30 and 50ms when content is replaced.
32
     *
33
     * @return void
34
     */
35
    public function addVersionToJsImports(): void
36
    {
37
        // $start = hrtime(true);
38
        if (is_dir($this->assetPath)) {
39
            $rii = new RecursiveIteratorIterator(
40
                new RecursiveDirectoryIterator($this->assetPath, FilesystemIterator::SKIP_DOTS)
41
            );
42
43
            foreach ($rii as $file) {
44
                $fileInfo = pathinfo($file->getPathname());
45
46
                if (isset($fileInfo['extension']) && $fileInfo['extension'] === 'js') {
47
                    $content = file_get_contents($file->getPathname()) ?: '';
48
                    $originalContent = $content;
49
                    // Matches lines that have 'import ' then any string then ' from ' and single or double quote opening then
50
                    // any string (path) then '.js' and optionally v GET param '?v=234' and '";' at the end with single or double quotes
51
                    preg_match_all('/import (.|\n|\r|\t)*? from ("|\')(.*?)\.js(\?v=.*?)?("|\');/', $content, $matches);
52
                    // $matches is an array that contains all matches. In this case, the content is the following:
53
                    // Key [0] is the entire matching string including the search
54
                    // Key [1] first variable unknown string after the 'import ' word (e.g. '{requestDropdownOptions}', '{createModal}')
55
                    // Key [2] single or double quotes of path opening after "from"
56
                    // Key [3] variable unknown string after the opening single or double quotes after from (only path) e.g.
57
                    // '../general/js/requestUtil/fail-handler'
58
                    // Key [4] optional '?v=2' GET param and [5] closing quotes
59
                    // Loop over import paths
60
                    foreach ($matches[3] as $key => $importPath) {
61
                        $oldFullImport = $matches[0][$key];
62
                        // Remove query params if the version is null
63
                        if ($this->version === null) {
64
                            $newImportPath = $importPath . '.js';
65
                        } else {
66
                            $newImportPath = $importPath . '.js?v=' . $this->version;
67
                        }
68
                        // Old import path potentially with GET param
69
                        $existingImportPath = $importPath . '.js' . $matches[4][$key];
70
                        // Search for old import path and replace with new one
71
                        $newFullImport = str_replace($existingImportPath, $newImportPath, $oldFullImport);
72
                        // Replace in file content
73
                        $content = str_replace($oldFullImport, $newFullImport, $content);
74
                    }
75
                    // Replace file contents with modified one if there are changes
76
                    if ($originalContent !== $content) {
77
                        file_put_contents($file->getPathname(), $content);
78
                    }
79
                }
80
            }
81
        }
82
        // Divided by a million gets milliseconds and a billion (+9) seconds
83
        // var_dump('Time used: ' . (hrtime(true) - $start) / 1e+6 . ' ms');
84
    }
85
}
86