Passed
Push — master ( 506152...7c24f3 )
by Alexander
05:14 queued 01:53
created

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