Passed
Push — master ( ffccba...8b9472 )
by Alexander
03:13 queued 48s
created

AssetLoader::withBaseUrl()   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
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
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
    /**
237
     * @param int|null $position Specifies where the `<style>` tag should be inserted in a page.
238
     *
239
     * @see AssetBundle::$cssPosition
240
     */
241 3
    public function withCssDefaultPosition(?int $position): self
242
    {
243 3
        $new = clone $this;
244 3
        $new->cssDefaultPosition = $position;
245 3
        return $new;
246
    }
247
248
    /**
249
     * Returns a new instance with the specified global `$js` default options for all assets bundle.
250
     *
251
     * @param array $jsDefaultOptions The options that will be passed to {@see \Yiisoft\View\WebView::registerJsFile()}
252
     * when registering the JS files all assets bundle.
253
     *
254
     * @return self
255
     */
256 1
    public function withJsDefaultOptions(array $jsDefaultOptions): self
257
    {
258 1
        $new = clone $this;
259 1
        $new->jsDefaultOptions = $jsDefaultOptions;
260 1
        return $new;
261
    }
262
263
    /**
264
     * @param int|null $position Specifies where the `<script>` tag should be inserted in a page.
265
     *
266
     * @see AssetBundle::$jsPosition
267
     */
268 4
    public function withJsDefaultPosition(?int $position): self
269
    {
270 4
        $new = clone $this;
271 4
        $new->jsDefaultPosition = $position;
272 4
        return $new;
273
    }
274
275
    /**
276
     * If the asset bundle does not have a {@see AssetBundle::$basePath} set, the value set with {@see withBasePath()}
277
     * is returned.
278
     *
279
     * @param AssetBundle $bundle
280
     *
281
     * @return string|null
282
     */
283 57
    private function getBundleBasePath(AssetBundle $bundle): ?string
284
    {
285 57
        if ($bundle->basePath === null && $this->basePath === null) {
286 10
            return null;
287
        }
288
289 49
        return $this->aliases->get($bundle->basePath ?? (string) $this->basePath);
290
    }
291
292
    /**
293
     * If the asset bundle does not have a {@see AssetBundle::$baseUrl} set, the value set with {@see withBaseUrl()}
294
     * is returned.
295
     *
296
     * @param AssetBundle $bundle
297
     *
298
     * @return string|null
299
     */
300 57
    private function getBundleBaseUrl(AssetBundle $bundle): ?string
301
    {
302 57
        if ($bundle->baseUrl === null && $this->baseUrl === null) {
303 10
            return null;
304
        }
305
306 49
        return $this->aliases->get($bundle->baseUrl ?? (string) $this->baseUrl);
307
    }
308
}
309