Completed
Push — master ( 3a7ed1...6676dd )
by Lars
01:49
created

HtmlMinDomObserverOptimizeAttributes   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 199
Duplicated Lines 5.03 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 91.18%

Importance

Changes 0
Metric Value
wmc 64
lcom 1
cbo 3
dl 10
loc 199
ccs 62
cts 68
cp 0.9118
rs 3.28
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A domElementBeforeMinification() 0 3 1
C domElementAfterMinification() 0 57 16
F removeAttributeHelper() 10 65 42
A sortCssClassNames() 0 22 5

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 array
19
     */
20
    private static $executableScriptsMimeTypes = [
21
        'text/javascript'          => '',
22
        'text/ecmascript'          => '',
23
        'text/jscript'             => '',
24
        'application/javascript'   => '',
25
        'application/x-javascript' => '',
26
        'application/ecmascript'   => '',
27
    ];
28
29
    /**
30
     * Receive dom elements before the minification.
31
     *
32
     * @param SimpleHtmlDom $element
33
     * @param HtmlMin       $htmlMin
34
     */
35 33
    public function domElementBeforeMinification(SimpleHtmlDom $element, HtmlMin $htmlMin) {
36
37 33
    }
38
39
    /**
40
     * Receive dom elements after the minification.
41
     *
42
     * @param SimpleHtmlDom $element
43
     * @param HtmlMin       $htmlMin
44
     */
45 33
    public function domElementAfterMinification(SimpleHtmlDom $element, HtmlMin $htmlMin) {
46 33
        $attributes = $element->getAllAttributes();
47 33
        if ($attributes === null) {
48 33
            return;
49
        }
50
51 20
        $attrs = [];
52 20
        foreach ((array) $attributes as $attrName => $attrValue) {
53
54
            // -------------------------------------------------------------------------
55
            // Remove optional "http:"-prefix from attributes.
56
            // -------------------------------------------------------------------------
57
58 20
            if ($htmlMin->isDoRemoveHttpPrefixFromAttributes() === true) {
59
                if (
60 3
                    ($attrName === 'href' || $attrName === 'src' || $attrName === 'action')
61
                    &&
62 3
                    !(isset($attributes['rel']) && $attributes['rel'] === 'external')
63
                    &&
64 3
                    !(isset($attributes['target']) && $attributes['target'] === '_blank')
65
                ) {
66 2
                    $attrValue = \str_replace('http://', '//', $attrValue);
67
                }
68
            }
69
70 20
            if ($this->removeAttributeHelper($element->tag, $attrName, $attrValue, $attributes, $htmlMin)) {
71 4
                $element->{$attrName} = null;
72
73 4
                continue;
74
            }
75
76
            // -------------------------------------------------------------------------
77
            // Sort css-class-names, for better gzip results.
78
            // -------------------------------------------------------------------------
79
80 20
            if ($htmlMin->isDoSortCssClassNames() === true) {
81 19
                $attrValue = $this->sortCssClassNames($attrName, $attrValue);
82
            }
83
84 20
            if ($htmlMin->isDoSortHtmlAttributes() === true) {
85 19
                $attrs[$attrName] = $attrValue;
86 20
                $element->{$attrName} = null;
87
            }
88
        }
89
90
        // -------------------------------------------------------------------------
91
        // Sort html-attributes, for better gzip results.
92
        // -------------------------------------------------------------------------
93
94 20
        if ($htmlMin->isDoSortHtmlAttributes() === true) {
95 19
            \ksort($attrs);
96 19
            foreach ($attrs as $attrName => $attrValue) {
97 19
                $attrValue = HtmlDomParser::replaceToPreserveHtmlEntities($attrValue);
98 19
                $element->setAttribute($attrName, $attrValue, true);
99
            }
100
        }
101 20
    }
102
103
104
    /**
105
     * Check if the attribute can be removed.
106
     *
107
     * @param string  $tag
108
     * @param string  $attrName
109
     * @param string  $attrValue
110
     * @param array   $allAttr
111
     * @param HtmlMin $htmlMin
112
     *
113
     * @return bool
114
     */
115 20
    private function removeAttributeHelper($tag, $attrName, $attrValue, $allAttr, HtmlMin $htmlMin): bool
116
    {
117
        // remove defaults
118 20
        if ($htmlMin->isDoRemoveDefaultAttributes() === true) {
119 1
            if ($tag === 'script' && $attrName === 'language' && $attrValue === 'javascript') {
120
                return true;
121
            }
122
123 1
            if ($tag === 'form' && $attrName === 'method' && $attrValue === 'get') {
124
                return true;
125
            }
126
127 1
            if ($tag === 'input' && $attrName === 'type' && $attrValue === 'text') {
128
                return true;
129
            }
130
131 1
            if ($tag === 'area' && $attrName === 'shape' && $attrValue === 'rect') {
132
                return true;
133
            }
134
        }
135
136
        // remove deprecated charset-attribute (the browser will use the charset from the HTTP-Header, anyway)
137 20
        if ($htmlMin->isDoRemoveDeprecatedScriptCharsetAttribute() === true) {
138 19
            if ($tag === 'script' && $attrName === 'charset' && !isset($allAttr['src'])) {
139
                return true;
140
            }
141
        }
142
143
        // remove deprecated anchor-jump
144 20
        if ($htmlMin->isDoRemoveDeprecatedAnchorName() === true) {
145 19
            if ($tag === 'a' && $attrName === 'name' && isset($allAttr['id']) && $allAttr['id'] === $attrValue) {
146
                return true;
147
            }
148
        }
149
150
        // remove "type=text/css" for css links
151 20 View Code Duplication
        if ($htmlMin->isDoRemoveDeprecatedTypeFromStylesheetLink() === true) {
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...
152 19
            if ($tag === 'link' && $attrName === 'type' && $attrValue === 'text/css' && isset($allAttr['rel']) && $allAttr['rel'] === 'stylesheet') {
153 1
                return true;
154
            }
155
        }
156
157
        // remove deprecated script-mime-types
158 20
        if ($htmlMin->isDoRemoveDeprecatedTypeFromScriptTag() === true) {
159 19
            if ($tag === 'script' && $attrName === 'type' && isset($allAttr['src'], self::$executableScriptsMimeTypes[$attrValue])) {
160 1
                return true;
161
            }
162
        }
163
164
        // remove 'value=""' from <input type="text">
165 20 View Code Duplication
        if ($htmlMin->isDoRemoveValueFromEmptyInput() === true) {
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...
166 19
            if ($tag === 'input' && $attrName === 'value' && $attrValue === '' && isset($allAttr['type']) && $allAttr['type'] === 'text') {
167 1
                return true;
168
            }
169
        }
170
171
        // remove some empty attributes
172 20
        if ($htmlMin->isDoRemoveEmptyAttributes() === true) {
173 19
            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)) {
174 3
                return true;
175
            }
176
        }
177
178 20
        return false;
179
    }
180
181
182
    /**
183
     * @param $attrName
184
     * @param $attrValue
185
     *
186
     * @return string
187
     */
188 19
    private function sortCssClassNames($attrName, $attrValue): string
189
    {
190 19
        if ($attrName !== 'class' || !$attrValue) {
191 16
            return $attrValue;
192
        }
193
194 10
        $classes = \array_unique(
195 10
            \explode(' ', $attrValue)
196
        );
197 10
        \sort($classes);
198
199 10
        $attrValue = '';
200 10
        foreach ($classes as $class) {
201 10
            if (!$class) {
202 3
                continue;
203
            }
204
205 10
            $attrValue .= \trim($class) . ' ';
206
        }
207
208 10
        return \trim($attrValue);
209
    }
210
}
211