Completed
Push — master ( f1c416...8a91b5 )
by Sang
01:40
created

Assets::getScriptItem()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
3
namespace Botble\Assets;
4
5
use Illuminate\Config\Repository;
6
7
/**
8
 * Class Assets.
9
 *
10
 * @since 22/07/2015 11:23 PM
11
 */
12
class Assets
13
{
14
    /**
15
     * @var Repository
16
     */
17
    protected $config;
18
19
    /**
20
     * @var HtmlBuilder
21
     */
22
    protected $htmlBuilder;
23
24
    /**
25
     * @var array
26
     */
27
    protected $scripts = [];
28
29
    /**
30
     * @var array
31
     */
32
    protected $styles = [];
33
34
    /**
35
     * @var array
36
     */
37
    protected $appendedScripts = [
38
        'header' => [],
39
        'footer' => [],
40
    ];
41
42
    /**
43
     * @var array
44
     */
45
    protected $appendedStyles = [];
46
47
    /**
48
     * @var string
49
     */
50
    protected $build = '';
51
52
    const ASSETS_SCRIPT_POSITION_HEADER = 'header';
53
54
    const ASSETS_SCRIPT_POSITION_FOOTER = 'footer';
55
56
    /**
57
     * Assets constructor.
58
     *
59
     * @param Repository $config
60
     * @param HtmlBuilder $htmlBuilder
61
     */
62
    public function __construct(Repository $config, HtmlBuilder $htmlBuilder)
63
    {
64
        $this->config = $config->get('assets');
65
66
        $this->scripts = $this->config['scripts'];
67
68
        $this->styles = $this->config['styles'];
69
70
        $this->htmlBuilder = $htmlBuilder;
71
    }
72
73
    /**
74
     * Add scripts to current module.
75
     *
76
     * @param array $assets
77
     * @return $this
78
     */
79
    public function addScripts($assets)
80
    {
81
        $this->scripts = array_merge($this->scripts, (array)$assets);
82
83
        return $this;
84
    }
85
86
    /**
87
     * Add Css to current module.
88
     *
89
     * @param array $assets
90
     * @return $this
91
     */
92
    public function addStyles($assets)
93
    {
94
        $this->styles = array_merge($this->styles, (array)$assets);
95
96
        return $this;
97
    }
98
99
    /**
100
     * Add styles directly.
101
     *
102
     * @param array|string $assets
103
     * @return $this
104
     */
105
    public function addStylesDirectly($assets)
106
    {
107 View Code Duplication
        foreach ((array)$assets as &$item) {
0 ignored issues
show
Bug introduced by
The expression (array) $assets cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
108
            if (!in_array($item, $this->appendedStyles)) {
109
                $this->appendedStyles[] = [
110
                    'src'        => $item,
111
                    'attributes' => [],
112
                ];
113
            }
114
        }
115
116
        return $this;
117
    }
118
119
    /**
120
     * Add scripts directly.
121
     *
122
     * @param string|array $assets
123
     * @param string $location
124
     * @return $this
125
     */
126
    public function addScriptsDirectly($assets, $location = self::ASSETS_SCRIPT_POSITION_FOOTER)
127
    {
128 View Code Duplication
        foreach ((array)$assets as &$item) {
0 ignored issues
show
Bug introduced by
The expression (array) $assets cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
129
            if (!in_array($item, $this->appendedScripts[$location])) {
130
                $this->appendedScripts[$location][] = [
131
                    'src'        => $item,
132
                    'attributes' => [],
133
                ];
134
            }
135
        }
136
137
        return $this;
138
    }
139
140
    /**
141
     * Remove Css to current module.
142
     *
143
     * @param array $assets
144
     * @return $this
145
     */
146
    public function removeStyles($assets)
147
    {
148
        foreach ((array)$assets as $rem) {
149
            array_forget($this->styles, array_search($rem, $this->styles));
150
        }
151
152
        return $this;
153
    }
154
155
    /**
156
     * Add scripts.
157
     *
158
     * @param array $assets
159
     * @return $this
160
     */
161
    public function removeScripts($assets)
162
    {
163
        foreach ((array)$assets as $rem) {
164
            array_forget($this->scripts, array_search($rem, $this->scripts));
165
        }
166
167
        return $this;
168
    }
169
170
    /**
171
     * Get All scripts in current module.
172
     *
173
     * @param string $location `header` or `footer`
174
     * @return array
175
     */
176
    public function getScripts($location = null)
177
    {
178
        $scripts = [];
179
180
        $this->scripts = array_unique($this->scripts);
181
182
        foreach ($this->scripts as $script) {
183
            $configName = 'resources.scripts.' . $script;
184
185
            if (!empty($location) && $location !== array_get($this->config, $configName . '.location')) {
186
                continue; // Skip assets that don't match this location
187
            }
188
189
            $scripts = array_merge($scripts, $this->getScriptItem($location, $configName, $script));
190
        }
191
192
        if (isset($this->appendedScripts[$location])) {
193
            $scripts = array_merge($scripts, $this->appendedScripts[$location]);
194
        }
195
196
        return $scripts;
197
    }
198
199
    /**
200
     * Get All CSS in current module.
201
     *
202
     * @param array $lastStyles Append last CSS to current module
203
     * @return array
204
     */
205
    public function getStyles($lastStyles = [])
206
    {
207
        $styles = [];
208
        if (!empty($lastStyles)) {
209
            $this->styles = array_merge($this->styles, $lastStyles);
210
        }
211
212
        $this->styles = array_unique($this->styles);
213
214
        foreach ($this->styles as $style) {
215
            $configName = 'resources.styles.' . $style;
216
217
            $styles = array_merge($styles, $this->getSource($configName));
218
        }
219
220
        return array_merge($styles, $this->appendedStyles);
221
    }
222
223
    /**
224
     * Convert script to html.
225
     *
226
     * @param string $name
227
     * @return  string|null
228
     */
229
    public function scriptToHtml($name)
230
    {
231
        return $this->itemToHtml($name, 'script');
232
    }
233
234
    /**
235
     * Convert style to html.
236
     *
237
     * @param string $name
238
     */
239
    public function styleToHtml($name)
240
    {
241
        return $this->itemToHtml($name, 'style');
242
    }
243
244
    /**
245
     * Render assets to header.
246
     *
247
     * @param array $lastStyles
248
     * @return string
249
     * @throws \Throwable
250
     */
251
    public function renderHeader($lastStyles = [])
252
    {
253
        $styles = $this->getStyles($lastStyles);
254
255
        $headScripts = $this->getScripts(self::ASSETS_SCRIPT_POSITION_HEADER);
256
257
        return view('assets::header', compact('styles', 'headScripts'))->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
258
    }
259
260
    /**
261
     * Render assets to footer.
262
     *
263
     * @return string
264
     * @throws \Throwable
265
     */
266
    public function renderFooter()
267
    {
268
        $bodyScripts = $this->getScripts(self::ASSETS_SCRIPT_POSITION_FOOTER);
269
270
        return view('assets::footer', compact('bodyScripts'))->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
271
    }
272
273
    /**
274
     * Get script item.
275
     *
276
     * @param string $location
277
     * @param string $configName
278
     * @param string $script
279
     * @return array
280
     */
281
    protected function getScriptItem($location, $configName, $script)
282
    {
283
        $scripts = $this->getSource($configName, $location);
284
285
        if (array_get($this->config, $configName . '.include_style')) {
286
            $this->addStyles([$script]);
287
        }
288
289
        return $scripts;
290
    }
291
292
    /**
293
     * Fallback to local script if CDN fails.
294
     *
295
     * @param string $src
296
     * @param string $configName
297
     * @return array
298
     */
299
    protected function getFallbackScript($src, $configName)
300
    {
301
        return [
302
            'src'         => $src,
303
            'fallback'    => array_get($this->config, $configName . '.fallback'),
304
            'fallbackURL' => array_get($this->config, $configName . '.src.local'),
305
        ];
306
    }
307
308
    /**
309
     * Convert item to html.
310
     *
311
     * @param string $name
312
     * @param string $type
313
     * @return null|string
314
     */
315
    protected function itemToHtml($name, $type = 'style')
316
    {
317
        $html = '';
318
319
        if (!in_array($type, ['style', 'script'])) {
320
            return $html;
321
        }
322
323
        $configName = 'resources.' . $type . 's.' . $name;
324
325
        if (!array_has($this->config, $configName)) {
326
            return $html;
327
        }
328
329
        $src = $this->getSourceUrl($configName);
330
331
        foreach ((array)$src as $item) {
332
            $html .= $this->{$type}($item, ['class' => 'hidden'])->toHtml();
333
        }
334
335
        return $html;
336
    }
337
338
    /**
339
     * @param $configName
340
     * @return null|string|array
341
     */
342
    protected function getSourceUrl($configName)
343
    {
344
        if (!array_has($this->config, $configName)) {
345
            return null;
346
        }
347
348
        $src = array_get($this->config, $configName . '.src.local');
349
350
        if ($this->isUsingCdn($configName)) {
351
            $src = array_get($this->config, $configName . '.src.cdn');
352
        }
353
354
        return $src;
355
    }
356
357
    /**
358
     * @param $configName
359
     * @return bool
360
     */
361
    protected function isUsingCdn($configName)
362
    {
363
        return array_get($this->config, $configName . '.use_cdn', false) && !$this->config['offline'];
364
    }
365
366
    /**
367
     * @param $configName
368
     * @return array
369
     */
370
    protected function getSource($configName, $location = null)
371
    {
372
        $isUsingCdn = $this->isUsingCdn($configName);
373
374
        $attributes = $isUsingCdn ? [] : array_get($this->config, $configName . '.attributes', []);
375
376
        $src = $this->getSourceUrl($configName);
377
378
        $scripts = [];
379
380
        foreach ((array)$src as $s) {
381
            $scripts[] = [
382
                'src'        => $s,
383
                'attributes' => $attributes,
384
            ];
385
        }
386
387
        if (empty($src) &&
388
            $isUsingCdn &&
389
            $location === self::ASSETS_SCRIPT_POSITION_HEADER &&
390
            array_has($this->config, $configName . '.fallback')) {
391
            $scripts[] = $this->getFallbackScript($src, $configName);
0 ignored issues
show
Bug introduced by
It seems like $src defined by $this->getSourceUrl($configName) on line 376 can also be of type array or null; however, Botble\Assets\Assets::getFallbackScript() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
392
        }
393
394
        return $scripts;
395
    }
396
397
    /**
398
     * @return string
399
     */
400
    public function getBuildVersion()
401
    {
402
        return $this->build = $this->config['enable_version'] ? '?v=' . $this->config['version'] : '';
403
    }
404
405
    /**
406
     * @return HtmlBuilder
407
     */
408
    public function getHtmlBuilder()
409
    {
410
        return $this->htmlBuilder;
411
    }
412
}
413