Completed
Push — master ( b15dfb...23d101 )
by Sang
05:50
created

Assets   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 399
Duplicated Lines 7.02 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 1
dl 28
loc 399
rs 8.4
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A getFallbackScript() 0 8 1
A __construct() 0 10 1
A addScripts() 0 6 1
A addStyles() 0 6 1
A addStylesDirectly() 8 13 3
A addScriptsDirectly() 8 13 3
A removeStyles() 0 8 2
A removeScripts() 0 8 2
B getScripts() 0 26 6
B getStyles() 4 36 7
A scriptToHtml() 0 4 1
A styleToHtml() 0 4 1
A renderHeader() 0 8 1
A renderFooter() 0 6 1
B getScriptItem() 5 38 9
B itemToHtml() 3 28 7
A getBuildVersion() 0 4 2
A getHtmlBuilder() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Assets often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Assets, and based on these observations, apply Extract Interface, too.

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 (!array_has($this->config, $configName)) {
186
                continue;
187
            }
188
189
            if (!empty($location) && $location !== array_get($this->config, $configName . '.location')) {
190
                continue; // Skip assets that don't match this location
191
            }
192
193
            $scripts = array_merge($scripts, $this->getScriptItem($location, $configName, $script));
194
        }
195
196
        if (isset($this->appendedScripts[$location])) {
197
            $scripts = array_merge($scripts, $this->appendedScripts[$location]);
198
        }
199
200
        return $scripts;
201
    }
202
203
    /**
204
     * Get All CSS in current module.
205
     *
206
     * @param array $lastStyles Append last CSS to current module
207
     * @return array
208
     */
209
    public function getStyles($lastStyles = [])
210
    {
211
        $styles = [];
212
        if (!empty($lastStyles)) {
213
            $this->styles = array_merge($this->styles, $lastStyles);
214
        }
215
216
        $this->styles = array_unique($this->styles);
217
218
        foreach ($this->styles as $style) {
219
            $configName = 'resources.styles.' . $style;
220
221
            if (!array_has($this->config, $configName)) {
222
                continue;
223
            }
224
225
            $src = array_get($this->config, $configName . '.src.local');
226
227
            $attributes = array_get($this->config, $configName . '.attributes', []);
228
229 View Code Duplication
            if (array_get($this->config, $configName . '.use_cdn') && !$this->config['offline']) {
0 ignored issues
show
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...
230
                $src = array_get($this->config, $configName . '.src.cdn');
231
232
                $attributes = [];
233
            }
234
235
            foreach ((array)$src as $s) {
236
                $styles[] = [
237
                    'src'        => $s,
238
                    'attributes' => $attributes,
239
                ];
240
            }
241
        }
242
243
        return array_merge($styles, $this->appendedStyles);
244
    }
245
246
    /**
247
     * Convert script to html.
248
     *
249
     * @param string $name
250
     * @return  string|null
251
     */
252
    public function scriptToHtml($name)
253
    {
254
        return $this->itemToHtml($name, 'script');
255
    }
256
257
    /**
258
     * Convert style to html.
259
     *
260
     * @param string $name
261
     */
262
    public function styleToHtml($name)
263
    {
264
        return $this->itemToHtml($name, 'style');
265
    }
266
267
    /**
268
     * Render assets to header.
269
     *
270
     * @param array $lastStyles
271
     * @return string
272
     * @throws \Throwable
273
     */
274
    public function renderHeader($lastStyles = [])
275
    {
276
        $styles = $this->getStyles($lastStyles);
277
278
        $headScripts = $this->getScripts(self::ASSETS_SCRIPT_POSITION_HEADER);
279
280
        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...
281
    }
282
283
    /**
284
     * Render assets to footer.
285
     *
286
     * @return string
287
     * @throws \Throwable
288
     */
289
    public function renderFooter()
290
    {
291
        $bodyScripts = $this->getScripts(self::ASSETS_SCRIPT_POSITION_FOOTER);
292
293
        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...
294
    }
295
296
    /**
297
     * Get script item.
298
     *
299
     * @param string $location
300
     * @param string $configName
301
     * @param string $script
302
     * @return array
303
     */
304
    protected function getScriptItem($location, $configName, $script)
305
    {
306
        $scripts = [];
307
308
        $src = array_get($this->config, $configName . '.src.local');
309
310
        $cdn = false;
311
312
        $attributes = array_get($this->config, $configName . '.attributes', []);
313
314 View Code Duplication
        if (array_get($this->config, $configName . '.use_cdn') && !$this->config['offline']) {
0 ignored issues
show
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...
315
            $src = array_get($this->config, $configName . '.src.cdn');
316
317
            $cdn = true;
318
319
            $attributes = [];
320
        }
321
322
        foreach ((array)$src as $s) {
323
            $scripts[] = [
324
                'src'        => $s,
325
                'attributes' => $attributes,
326
            ];
327
        }
328
329
        if (empty($src) &&
330
            $cdn &&
331
            $location === self::ASSETS_SCRIPT_POSITION_HEADER &&
332
            array_has($this->config, $configName . '.fallback')) {
333
            $scripts[] = $this->getFallbackScript($src, $configName);
334
        }
335
336
        if (array_get($this->config, $configName . '.include_style')) {
337
            $this->addStyles([$script]);
338
        }
339
340
        return $scripts;
341
    }
342
343
    /**
344
     * Fallback to local script if CDN fails.
345
     *
346
     * @param string $src
347
     * @param string $configName
348
     * @return array
349
     */
350
    protected function getFallbackScript($src, $configName)
351
    {
352
        return [
353
            'src'         => $src,
354
            'fallback'    => array_get($this->config, $configName . '.fallback'),
355
            'fallbackURL' => array_get($this->config, $configName . '.src.local'),
356
        ];
357
    }
358
359
    /**
360
     * Convert item to html.
361
     *
362
     * @param string $name
363
     * @param string $type
364
     * @return null|string
365
     */
366
    protected function itemToHtml($name, $type = 'style')
367
    {
368
        $html = '';
369
370
        if (!in_array($type, ['style', 'script'])) {
371
            return $html;
372
        }
373
374
        $config = 'resources.styles.' . $name;
375
376
        if ($type === 'script') {
377
            $config = 'resources.scripts.' . $name;
378
        }
379
380
        if (array_has($this->config, $config)) {
381
            $src = array_get($this->config, $config . '.src.local');
382
383 View Code Duplication
            if (array_get($this->config, $config . '.use_cdn') && !$this->config['offline']) {
0 ignored issues
show
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...
384
                $src = array_get($this->config, $config . '.src.cdn');
385
            }
386
387
            foreach ((array)$src as $item) {
388
                $html .= $this->{$type}($item, ['class' => 'hidden'])->toHtml();
389
            }
390
        }
391
392
        return $html;
393
    }
394
395
    /**
396
     * @return string
397
     */
398
    public function getBuildVersion()
399
    {
400
        return $this->build = $this->config['enable_version'] ? '?v=' . $this->config['version'] : '';
401
    }
402
403
    /**
404
     * @return HtmlBuilder
405
     */
406
    public function getHtmlBuilder()
407
    {
408
        return $this->htmlBuilder;
409
    }
410
}
411