Completed
Push — master ( f73514...b3b2b5 )
by Lars
17s queued 13s
created

HtmlMinDomObserverOptimizeAttributes   F

Complexity

Total Complexity 78

Size/Duplication

Total Lines 263
Duplicated Lines 14.45 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 91.76%

Importance

Changes 0
Metric Value
wmc 78
lcom 1
cbo 3
dl 38
loc 263
ccs 78
cts 85
cp 0.9176
rs 2.16
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A domElementBeforeMinification() 0 3 1
F removeAttributeHelper() 20 71 42
B removeHttpPrefixHelper() 9 20 10
A sortCssClassNames() 0 22 5
F domElementAfterMinification() 9 82 20

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 HtmlMinDomObserverOptimizeAttributes 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 HtmlMinDomObserverOptimizeAttributes, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
/**
8
 * HtmlMinDomObserverOptimizeAttributes: Optimize html attributes. [protected html is still protected]
9
 *
10
 * Sort HTML-Attributes, so that gzip can do better work and remove some default attributes...
11
 */
12
final class HtmlMinDomObserverOptimizeAttributes implements HtmlMinDomObserverInterface
13
{
14
    /**
15
     * // https://mathiasbynens.be/demo/javascript-mime-type
16
     * // https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
17
     *
18
     * @var string[]
19
     *
20
     * @psalm-var array<string, string>
21
     */
22
    private static $executableScriptsMimeTypes = [
23
        'text/javascript'          => '',
24
        'text/ecmascript'          => '',
25
        'text/jscript'             => '',
26
        'application/javascript'   => '',
27
        'application/x-javascript' => '',
28
        'application/ecmascript'   => '',
29
    ];
30
31
    /**
32
     * Receive dom elements before the minification.
33
     *
34
     * @param SimpleHtmlDomInterface $element
35
     * @param HtmlMinInterface       $htmlMin
36
     *
37
     * @return void
38
     */
39 51
    public function domElementBeforeMinification(SimpleHtmlDomInterface $element, HtmlMinInterface $htmlMin)
40
    {
41 51
    }
42
43
    /**
44
     * Receive dom elements after the minification.
45
     *
46
     * @param SimpleHtmlDomInterface $element
47
     * @param HtmlMinInterface       $htmlMin
48
     *
49
     * @return void
50
     */
51 51
    public function domElementAfterMinification(SimpleHtmlDomInterface $element, HtmlMinInterface $htmlMin)
52
    {
53 51
        $attributes = $element->getAllAttributes();
54 51
        if ($attributes === null) {
55 48
            return;
56
        }
57
58 34
        $attrs = [];
59 34
        foreach ((array) $attributes as $attrName => $attrValue) {
60
61
            // -------------------------------------------------------------------------
62
            // Remove optional "http:"-prefix from attributes.
63
            // -------------------------------------------------------------------------
64
65 34
            if ($htmlMin->isDoRemoveHttpPrefixFromAttributes()) {
66 5
                $attrValue = $this->removeHttpPrefixHelper(
67 5
                    $attrValue,
68
                    $attrName,
69 5
                    'http',
70
					$attributes,
71
					$htmlMin
72
                );
73
            }
74
75 34
            if ($htmlMin->isDoRemoveHttpsPrefixFromAttributes()) {
76 1
                $attrValue = $this->removeHttpPrefixHelper(
77 1
                    $attrValue,
78
                    $attrName,
79 1
                    'https',
80
                    $attributes,
81
					$htmlMin
82
                );
83
            }
84
85 34
            if ($htmlMin->isDoMakeSameDomainLinksRelative()) {
86 2
				if (!$htmlMin->isLocalDomainSet()){
87
					$htmlMin->setLocalDomain();
88
				}
89 2
				$localDomain = $htmlMin->getLocalDomain();
90 View Code Duplication
				if (
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...
91 2
					(($attrName === 'href') || $attrName === 'src' || $attrName === 'srcset' || $attrName === 'action')
92
					&&
93 2
					!(isset($attributes['rel']) && $attributes['rel'] === 'external')
94
					&&
95 2
					!(isset($attributes['target']) && $attributes['target'] === '_blank')
96
				) {
97 2
					$attrValue = \preg_replace("/^((https?:)?\/\/)?{$localDomain}(?!\w)(\/?)/", '/', $attrValue);
98
				}
99
            }
100
101 34
            if ($this->removeAttributeHelper($element->tag, $attrName, $attrValue, $attributes, $htmlMin)) {
0 ignored issues
show
Bug introduced by
Accessing tag on the interface voku\helper\SimpleHtmlDomInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
102 6
                $element->{$attrName} = null;
103
104 6
                continue;
105
            }
106
107
            // -------------------------------------------------------------------------
108
            // Sort css-class-names, for better gzip results.
109
            // -------------------------------------------------------------------------
110
111 34
            if ($htmlMin->isDoSortCssClassNames()) {
112 33
                $attrValue = $this->sortCssClassNames($attrName, $attrValue);
113
            }
114
115 34
            if ($htmlMin->isDoSortHtmlAttributes()) {
116 33
                $attrs[$attrName] = $attrValue;
117 33
                $element->{$attrName} = null;
118
            }
119
        }
120
121
        // -------------------------------------------------------------------------
122
        // Sort html-attributes, for better gzip results.
123
        // -------------------------------------------------------------------------
124
125 34
        if ($htmlMin->isDoSortHtmlAttributes()) {
126 33
            \ksort($attrs);
127 33
            foreach ($attrs as $attrName => $attrValue) {
128 33
                $attrValue = HtmlDomParser::replaceToPreserveHtmlEntities($attrValue);
129 33
                $element->setAttribute((string) $attrName, $attrValue, true);
130
            }
131
        }
132 34
    }
133
134
    /**
135
     * Check if the attribute can be removed.
136
     *
137
     * @param string  $tag
138
     * @param string  $attrName
139
     * @param string  $attrValue
140
     * @param array   $allAttr
141
     * @param HtmlMinInterface $htmlMin
142
     *
143
     * @return bool
144
     */
145 34
    private function removeAttributeHelper($tag, $attrName, $attrValue, $allAttr, HtmlMinInterface $htmlMin): bool
146
    {
147
        // remove defaults
148 34
        if ($htmlMin->isDoRemoveDefaultAttributes()) {
149 1
            if ($tag === 'script' && $attrName === 'language' && $attrValue === 'javascript') {
150
                return true;
151
            }
152
153 1
            if ($tag === 'form' && $attrName === 'method' && $attrValue === 'get') {
154
                return true;
155
            }
156
157 1
            if ($tag === 'input' && $attrName === 'type' && $attrValue === 'text') {
158
                return true;
159
            }
160
161 1
            if ($tag === 'area' && $attrName === 'shape' && $attrValue === 'rect') {
162
                return true;
163
            }
164
        }
165
166
        // remove deprecated charset-attribute (the browser will use the charset from the HTTP-Header, anyway)
167 34
        if ($htmlMin->isDoRemoveDeprecatedScriptCharsetAttribute()) {
168
            /** @noinspection NestedPositiveIfStatementsInspection */
169 33
            if ($tag === 'script' && $attrName === 'charset' && !isset($allAttr['src'])) {
170
                return true;
171
            }
172
        }
173
174
        // remove deprecated anchor-jump
175 34 View Code Duplication
        if ($htmlMin->isDoRemoveDeprecatedAnchorName()) {
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...
176
            /** @noinspection NestedPositiveIfStatementsInspection */
177 33
            if ($tag === 'a' && $attrName === 'name' && isset($allAttr['id']) && $allAttr['id'] === $attrValue) {
178
                return true;
179
            }
180
        }
181
182
        // remove "type=text/css" for css links
183 34 View Code Duplication
        if ($htmlMin->isDoRemoveDeprecatedTypeFromStylesheetLink()) {
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...
184
            /** @noinspection NestedPositiveIfStatementsInspection */
185 33
            if ($tag === 'link' && $attrName === 'type' && $attrValue === 'text/css' && isset($allAttr['rel']) && $allAttr['rel'] === 'stylesheet') {
186 1
                return true;
187
            }
188
        }
189
190
        // remove deprecated script-mime-types
191 34 View Code Duplication
        if ($htmlMin->isDoRemoveDeprecatedTypeFromScriptTag()) {
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...
192
            /** @noinspection NestedPositiveIfStatementsInspection */
193 33
            if ($tag === 'script' && $attrName === 'type' && isset($allAttr['src'], self::$executableScriptsMimeTypes[$attrValue])) {
194 1
                return true;
195
            }
196
        }
197
198
        // remove 'value=""' from <input type="text">
199 34 View Code Duplication
        if ($htmlMin->isDoRemoveValueFromEmptyInput()) {
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...
200
            /** @noinspection NestedPositiveIfStatementsInspection */
201 33
            if ($tag === 'input' && $attrName === 'value' && $attrValue === '' && isset($allAttr['type']) && $allAttr['type'] === 'text') {
202 1
                return true;
203
            }
204
        }
205
206
        // remove some empty attributes
207 34
        if ($htmlMin->isDoRemoveEmptyAttributes()) {
208
            /** @noinspection NestedPositiveIfStatementsInspection */
209 33
            if (\trim($attrValue) === '' && \preg_match('/^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(?:down|up|over|move|out)|key(?:press|down|up)))$/', $attrName)) {
210 5
                return true;
211
            }
212
        }
213
214 34
        return false;
215
    }
216
217
    /**
218
     * @param string $attrValue
219
     * @param string $attrName
220
     * @param string $scheme
221
     * @param array  $attributes
222
     *
223
     * @return string
224
     */
225 5
    private function removeHttpPrefixHelper(
226
        string $attrValue,
227
        string $attrName,
228
        string $scheme,
229
		array $attributes,
230
		HtmlMinInterface $htmlMin
231
    ): string {
232
        /** @noinspection InArrayCanBeUsedInspection */
233 View Code Duplication
        if (
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...
234 5
            (($attrName === 'href' && !$htmlMin->isKeepPrefixOnExternalAttributes()) || $attrName === 'src' || $attrName === 'srcset' || $attrName === 'action')
235
            &&
236 5
            !(isset($attributes['rel']) && $attributes['rel'] === 'external')
237
            &&
238 5
            !(isset($attributes['target']) && $attributes['target'] === '_blank')
239
        ) {
240 4
            $attrValue = \str_replace($scheme . '://', '//', $attrValue);
241
        }
242
243 5
        return $attrValue;
244
    }
245
246
    /**
247
     * @param string $attrName
248
     * @param string $attrValue
249
     *
250
     * @return string
251
     */
252 33
    private function sortCssClassNames($attrName, $attrValue): string
253
    {
254 33
        if ($attrName !== 'class' || !$attrValue) {
255 28
            return $attrValue;
256
        }
257
258 18
        $classes = \array_unique(
259 18
            \explode(' ', $attrValue)
260
        );
261 18
        \sort($classes);
262
263 18
        $attrValue = '';
264 18
        foreach ($classes as $class) {
265 18
            if (!$class) {
266 3
                continue;
267
            }
268
269 18
            $attrValue .= \trim($class) . ' ';
270
        }
271
272 18
        return \trim($attrValue);
273
    }
274
}
275