Passed
Pull Request — master (#74)
by Sergei
02:40
created

AssetLoader::withJsDefaultPosition()   A

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