AssetLoader::withJsDefaultOptions()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Assets;
6
7
use Yiisoft\Aliases\Aliases;
8
use Yiisoft\Assets\Exception\InvalidConfigException;
9
use Yiisoft\Files\FileHelper;
10
11
use function array_merge;
12
use function is_file;
13
14
/**
15
 * `AssetLoader` is responsible for executing the loading of the assets from {@see AssetBundle::$basePath} to
16
 * {@see AssetBundle::$baseUrl}.
17
 */
18
final class AssetLoader implements AssetLoaderInterface
19
{
20
    /**
21
     * @var array The options that will be passed to {@see \Yiisoft\View\WebView::registerCssFile()}
22
     * when registering the CSS files all assets bundle.
23
     */
24
    private array $cssDefaultOptions = [];
25
26
    private ?int $cssDefaultPosition = null;
27
28
    /**
29
     * @var array The options that will be passed to {@see \Yiisoft\View\WebView::registerJsFile()}
30
     * when registering the JS files all assets bundle.
31
     */
32
    private array $jsDefaultOptions = [];
33
34
    private ?int $jsDefaultPosition = null;
35
36
    /**
37
     * @param Aliases $aliases The aliases instance.
38
     * @param bool $appendTimestamp Whether to append a timestamp to the URL of every published asset. See
39
     *     {@see withAppendTimestamp()}.
40
     * @param array<string, string> $assetMap Mapping from source asset files to target asset files. See
41
     *     {@see withAssetMap()}.
42
     * @param string|null $basePath The root directory storing the asset files. See {@see withBasePath()}.
43
     * @param string|null $baseUrl The base URL that can be used to access the asset files. See {@see withBaseUrl()}.
44
     */
45 103
    public function __construct(
46
        private Aliases $aliases,
47
        private bool $appendTimestamp = false,
48
        private array $assetMap = [],
49
        private ?string $basePath = null,
50
        private ?string $baseUrl = null
51
    ) {
52 103
    }
53
54 50
    public function getAssetUrl(AssetBundle $bundle, string $assetPath): string
55
    {
56 50
        if (!$bundle->cdn && empty($this->basePath) && empty($bundle->basePath)) {
57 1
            throw new InvalidConfigException(
58 1
                'basePath must be set in AssetLoader->withBasePath($path) or ' .
59 1
                'AssetBundle property public ?string $basePath = $path'
60 1
            );
61
        }
62
63 49
        if (!$bundle->cdn && $this->baseUrl === null && $bundle->baseUrl === null) {
64 1
            throw new InvalidConfigException(
65 1
                'baseUrl must be set in AssetLoader->withBaseUrl($path) or ' .
66 1
                'AssetBundle property public ?string $baseUrl = $path'
67 1
            );
68
        }
69
70 48
        $asset = AssetUtil::resolveAsset($bundle, $assetPath, $this->assetMap);
71
72 48
        if (!empty($asset)) {
73 2
            $assetPath = $asset;
74
        }
75
76 48
        if ($bundle->cdn) {
77 4
            return $bundle->baseUrl === null
78 4
                ? $assetPath
79 4
                : $bundle->baseUrl . '/' . $assetPath;
80
        }
81
82 46
        if (!AssetUtil::isRelative($assetPath) || str_starts_with($assetPath, '/')) {
83 4
            return $assetPath;
84
        }
85
86 42
        $path = "{$this->getBundleBasePath($bundle)}/{$assetPath}";
87 42
        $url = "{$this->getBundleBaseUrl($bundle)}/{$assetPath}";
88
89 42
        if (!is_file($path)) {
90 1
            throw new InvalidConfigException("Asset files not found: \"{$path}\".");
91
        }
92
93 41
        if ($this->appendTimestamp && ($timestamp = FileHelper::lastModifiedTime($path)) > 0) {
94 2
            return "{$url}?v={$timestamp}";
95
        }
96
97 39
        return $url;
98
    }
99
100 75
    public function loadBundle(string $name, array $config = []): AssetBundle
101
    {
102 75
        $bundle = AssetUtil::createAsset($name, $config);
103
104 75
        $bundle->basePath = $this->getBundleBasePath($bundle);
105 75
        $bundle->baseUrl = $this->getBundleBaseUrl($bundle);
106 75
        $bundle->sourcePath = $bundle->sourcePath === null ? null : $this->aliases->get($bundle->sourcePath);
107
108 75
        $bundle->cssOptions = array_merge($bundle->cssOptions, $this->cssDefaultOptions);
109 75
        $bundle->cssPosition ??= $this->cssDefaultPosition;
110
111 75
        $bundle->jsOptions = array_merge($bundle->jsOptions, $this->jsDefaultOptions);
112 75
        $bundle->jsPosition ??= $this->jsDefaultPosition;
113
114 75
        return $bundle;
115
    }
116
117
    /**
118
     * Returns a new instance with the specified append timestamp.
119
     *
120
     * @param bool $appendTimestamp Whether to append a timestamp to the URL of every published asset. Default is `false`.
121
     * When this is true, the URL of a published asset may look like `/path/to/asset?v=timestamp`, where `timestamp`
122
     * is the last modification time of the published asset file. You normally would want to set this property to true
123
     * when you have enabled HTTP caching for assets, because it allows you to bust caching when the assets are updated.
124
     */
125 2
    public function withAppendTimestamp(bool $appendTimestamp): self
126
    {
127 2
        $new = clone $this;
128 2
        $new->appendTimestamp = $appendTimestamp;
129 2
        return $new;
130
    }
131
132
    /**
133
     * Returns a new instance with the specified asset map.
134
     *
135
     * @param array<string, string> $assetMap Mapping from source asset files (keys) to target asset files (values).
136
     *
137
     * Default is empty array. This property is provided to support fixing incorrect asset file paths in some
138
     * asset bundles. When an asset bundle is registered with a view, each relative asset file in its
139
     * {@see AssetBundle::$css} and {@see AssetBundle::$js} arrays will be examined against this map.
140
     * If any of the keys is found to be the last part of an asset file (which is prefixed with
141
     * {@see AssetBundle::$sourcePath} if available), the corresponding value will replace the asset
142
     * and be registered with the view. For example, an asset file `my/path/to/jquery.js` matches a key `jquery.js`.
143
     *
144
     * Note that the target asset files should be absolute URLs, domain relative URLs (starting from '/') or paths
145
     * relative to {@see withBaseUrl()} and {@see withBasePath()}.
146
     *
147
     * In the following example, any assets ending with `jquery.min.js` will be replaced with `jquery/dist/jquery.js`
148
     * which is relative to {@see withBaseUrl()} and {@see withBasePath()}.
149
     *
150
     * ```php
151
     * [
152
     *     'jquery.min.js' => 'jquery/dist/jquery.js',
153
     * ]
154
     * ```
155
     */
156 3
    public function withAssetMap(array $assetMap): self
157
    {
158 3
        $new = clone $this;
159 3
        $new->assetMap = $assetMap;
160 3
        return $new;
161
    }
162
163
    /**
164
     * Returns a new instance with the specified base path.
165
     *
166
     * @param string|null $basePath The root directory storing the asset files. Default is `null`.
167
     */
168 5
    public function withBasePath(?string $basePath): self
169
    {
170 5
        $new = clone $this;
171 5
        $new->basePath = $basePath;
172 5
        return $new;
173
    }
174
175
    /**
176
     * Returns a new instance with the specified base URL.
177
     *
178
     * @param string|null $baseUrl The base URL that can be used to access the asset files. Default is `null`.
179
     */
180 4
    public function withBaseUrl(?string $baseUrl): self
181
    {
182 4
        $new = clone $this;
183 4
        $new->baseUrl = $baseUrl;
184 4
        return $new;
185
    }
186
187
    /**
188
     * Returns a new instance with the specified global `$css` default options for all assets bundle.
189
     *
190
     * @param array $cssDefaultOptions The options that will be passed to
191
     * {@see \Yiisoft\View\WebView::registerCssFile()} when registering the CSS files all assets bundle.
192
     */
193 2
    public function withCssDefaultOptions(array $cssDefaultOptions): self
194
    {
195 2
        $new = clone $this;
196 2
        $new->cssDefaultOptions = $cssDefaultOptions;
197 2
        return $new;
198
    }
199
200
    /**
201
     * @param int|null $position Specifies where the `<style>` tag should be inserted in a page.
202
     *
203
     * @see AssetBundle::$cssPosition
204
     */
205 3
    public function withCssDefaultPosition(?int $position): self
206
    {
207 3
        $new = clone $this;
208 3
        $new->cssDefaultPosition = $position;
209 3
        return $new;
210
    }
211
212
    /**
213
     * Returns a new instance with the specified global `$js` default options for all assets bundle.
214
     *
215
     * @param array $jsDefaultOptions The options that will be passed to {@see \Yiisoft\View\WebView::registerJsFile()}
216
     * when registering the JS files all assets bundle.
217
     */
218 1
    public function withJsDefaultOptions(array $jsDefaultOptions): self
219
    {
220 1
        $new = clone $this;
221 1
        $new->jsDefaultOptions = $jsDefaultOptions;
222 1
        return $new;
223
    }
224
225
    /**
226
     * @param int|null $position Specifies where the `<script>` tag should be inserted in a page.
227
     *
228
     * @see AssetBundle::$jsPosition
229
     */
230 4
    public function withJsDefaultPosition(?int $position): self
231
    {
232 4
        $new = clone $this;
233 4
        $new->jsDefaultPosition = $position;
234 4
        return $new;
235
    }
236
237
    /**
238
     * If the asset bundle does not have a {@see AssetBundle::$basePath} set, the value set with {@see withBasePath()}
239
     * is returned.
240
     */
241 75
    private function getBundleBasePath(AssetBundle $bundle): ?string
242
    {
243 75
        if ($bundle->basePath === null && $this->basePath === null) {
244 10
            return null;
245
        }
246
247 67
        return $this->aliases->get($bundle->basePath ?? (string) $this->basePath);
248
    }
249
250
    /**
251
     * If the asset bundle does not have a {@see AssetBundle::$baseUrl} set, the value set with {@see withBaseUrl()}
252
     * is returned.
253
     */
254 75
    private function getBundleBaseUrl(AssetBundle $bundle): ?string
255
    {
256 75
        if ($bundle->baseUrl === null && $this->baseUrl === null) {
257 10
            return null;
258
        }
259
260 67
        return $this->aliases->get($bundle->baseUrl ?? (string) $this->baseUrl);
261
    }
262
}
263