Passed
Push — master ( 439363...848c74 )
by Bruno
03:23
created

HTMLElement   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 475
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 143
dl 0
loc 475
rs 2.88
c 0
b 0
f 0
wmc 69

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A setAttributes() 0 20 5
A prependContent() 0 4 1
B setContent() 0 25 7
A __clone() 0 8 3
A setTag() 0 4 1
A addAttributes() 0 4 1
B get() 0 27 6
A isEmpty() 0 3 1
A getRenderHTML() 0 3 1
A setRenderIfEmpty() 0 4 1
A appendContent() 0 4 1
A clearContent() 0 4 1
A getContent() 0 3 1
A getElements() 0 3 1
A removeAttribute() 0 6 2
A getInternal() 0 15 4
C __toString() 0 51 15
A addContent() 0 4 1
A getAttribute() 0 3 1
A addAttribute() 0 4 1
A factory() 0 3 1
A clearAttributes() 0 4 1
B match() 0 23 7
A getTag() 0 3 1
A setAttribute() 0 4 1
A getCountChildren() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like HTMLElement 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.

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

1
<?php
2
3
namespace Formularium;
4
5
/**
6
 * Class that encapsule DOM elements. Similar to PHP DOMElement but more flexible.
7
 * This is not used for parsing, but to build HTML.
8
 */
9
class HTMLElement
10
{
11
    /**
12
     * The HTML attributes and respectives values
13
     * This is an associative array wich:
14
     *     key is the attribute and
15
     *     value is the array with attributes values
16
     * @var array
17
     */
18
    protected $attributes = [];
19
20
    /**
21
     * Tag name
22
     * @var string
23
     */
24
    protected $tag;
25
26
    /**
27
     * The content of Element
28
     * @var array
29
     */
30
    protected $content = [];
31
32
    /**
33
     * If this tag has no children, still render it?
34
     * @var boolean
35
     */
36
    protected $renderIfEmpty = true;
37
38
    /**
39
     * Create a HTML Element
40
     * @param string $tag The tag name of Element
41
     * @param array $attributes The attribute with values
42
     * @param mixed $content The content of element, can be:
43
     *                - string (with text content)
44
     *                - HTMLElement
45
     *                - array with others elements or text
46
     * @param boolean $raw If true, do not escape content.
47
     */
48
    public function __construct(string $tag, array $attributes = [], $content = '', $raw = false)
49
    {
50
        $this->tag = $tag;
51
52
        $this->setAttributes($attributes);
53
54
        if (!empty($content)) {
55
            $this->setContent($content, true, $raw);
56
        }
57
    }
58
59
    /**
60
     * Create a HTML Element
61
     * @param string $tag The tag name of Element
62
     * @param array $attributes The attribute with values
63
     * @param mixed $content The content of element, can be:
64
     *                - string (with text content)
65
     *                - HTMLElement
66
     *                - array with others elements or text
67
     * @param boolean $raw If true, do not escape content.
68
     * @return HTMLElement
69
     */
70
    public static function factory(string $tag, array $attributes = [], $content = '', $raw = false): HTMLElement
71
    {
72
        return new self($tag, $attributes, $content, $raw);
73
    }
74
75
    /**
76
     * Sets whether this tag will render if it is empty (that is,
77
     * no contents or children)
78
     *
79
     * @param boolean $val
80
     * @return HTMLElement Itself
81
     */
82
    public function setRenderIfEmpty(bool $val): HTMLElement
83
    {
84
        $this->renderIfEmpty = $val;
85
        return $this;
86
    }
87
88
    /**
89
     * Return a tag
90
     * @return string tag name
91
     */
92
    public function getTag()
93
    {
94
        return $this->tag;
95
    }
96
97
    /**
98
     * Modifies our tag
99
     *
100
     * @param string $tag
101
     * @return HTMLElement Itself
102
     */
103
    public function setTag($tag)
104
    {
105
        $this->tag = $tag;
106
        return $this;
107
    }
108
109
    /**
110
     * Return a attribute
111
     * @param string $name The name of attribute
112
     * @return array Return the list of values, if attribute don't exist return empty array
113
     */
114
    public function getAttribute($name): array
115
    {
116
        return $this->attributes[$name] ?? [];
117
    }
118
119
    /**
120
     * Return the content of element
121
     * @return mixed array of HTMLElement and string (text)
122
     */
123
    public function getContent()
124
    {
125
        return $this->content;
126
    }
127
128
    public function isEmpty(): bool
129
    {
130
        return count($this->content) > 0;
131
    }
132
133
    /**
134
     *
135
     * @return int
136
     */
137
    public function getCountChildren(): int
138
    {
139
        return count($this->content);
140
    }
141
142
    /**
143
     * Set a attribute value, if attribute exist overwrite it
144
     * @param string $name
145
     * @param mixed $value Can be a string or array of string
146
     * @return HTMLElement Itself
147
     */
148
    public function setAttribute(string $name, $value): HTMLElement
149
    {
150
        $this->setAttributes([$name => $value], true);
151
        return $this;
152
    }
153
154
    /**
155
     * Add an attribute value, if attribute exist append value
156
     * @param string $name
157
     * @param mixed $value Can be a string or array of string
158
     * @return HTMLElement Itself
159
     */
160
    public function addAttribute(string $name, $value): HTMLElement
161
    {
162
        $this->setAttributes([$name => $value], false);
163
        return $this;
164
    }
165
166
    /**
167
     * Set a set of attributes
168
     * @param array $attributes Associative array wich
169
     * 				key is attributes names
170
     *              value is string or array values
171
     * @param bool $overwrite if true and attribute name exist overwrite them
172
     * @return HTMLElement Itself
173
     *
174
     */
175
    public function setAttributes(array $attributes, $overwrite = true): HTMLElement
176
    {
177
        foreach ($attributes as $atrib => $value) {
178
            if (is_array($value)) {
179
                $values = $value;
180
            } else {
181
                $values = [$value];
182
            }
183
184
            if ($overwrite) {
185
                $this->attributes[$atrib] = $values;
186
            } else {
187
                if (empty($this->attributes[$atrib])) {
188
                    $this->attributes[$atrib] = [];
189
                }
190
191
                $this->attributes[$atrib] = array_merge($this->attributes[$atrib], $values);
192
            }
193
        }
194
        return $this;
195
    }
196
197
    public function removeAttribute(string $attribute): HTMLElement
198
    {
199
        if (array_key_exists($attribute, $this->attributes)) {
200
            unset($this->attributes[$attribute]);
201
        }
202
        return $this;
203
    }
204
205
    /**
206
     * Aliases to HTMLElement::setAttributes($content, false);
207
     * @see setAttributes
208
     * @return HTMLElement Itself
209
     */
210
    public function addAttributes(array $attributes): HTMLElement
211
    {
212
        $this->setAttributes($attributes, false);
213
        return $this;
214
    }
215
216
    /**
217
     * Set content (dom objects or texts) to element
218
     * @param mixed $content The content of element, can be:
219
     *                - string (with text content)
220
     *                - HTMLElement
221
     *                - array with others elements or text
222
     * @param bool $overwrite if true overwrite content otherwise append the content
223
     * @param bool $raw If true, this is raw content (html) and should not be escaped.
224
     * @return HTMLElement Itself
225
     */
226
    public function setContent($content, $overwrite = true, $raw = false, $prepend = false): HTMLElement
227
    {
228
        // TODO Don't work with reference objects, change it
229
        if (!is_array($content)) {
230
            $content = [$content];
231
        }
232
233
        if ($raw == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
234
            foreach ($content as &$item) {
235
                if (is_string($item)) {
236
                    $item = htmlspecialchars($item);
237
                }
238
            }
239
        }
240
241
        if ($overwrite) {
242
            $this->content = $content;
243
        } else {
244
            if ($prepend) {
245
                $this->content = array_merge($content, $this->content);
246
            } else {
247
                $this->content = array_merge($this->content, $content);
248
            }
249
        }
250
        return $this;
251
    }
252
253
    /**
254
     * Aliases to HTMLElement::setContent($content, false);
255
     * @see setContent
256
     * @return HTMLElement Itself
257
     */
258
    public function addContent($content, bool $raw = false): HTMLElement
259
    {
260
        $this->setContent($content, false, $raw);
261
        return $this;
262
    }
263
264
    public function appendContent($content, bool $raw = false): HTMLElement
265
    {
266
        $this->setContent($content, false, $raw);
267
        return $this;
268
    }
269
270
    public function prependContent($content, bool $raw = false): HTMLElement
271
    {
272
        $this->setContent($content, false, $raw, true);
273
        return $this;
274
    }
275
276
    /**
277
     * Find and return elements using selector
278
     * @param string $selector A selector of elements based in jQuery
279
     *						'eee' - Select elements 'eee' (with tag)
280
     * 						'#ii' - Select a element with id attribute 'ii'
281
     * 						'.cc' - Select elements with class attribute 'cc'
282
     * 						'a=v' - Select elements with 'a' attribute with 'v' value
283
     * 						'e#i' - Select elements 'e' with id attribute 'i'
284
     * 						'e.c' - Select elements 'e' with class attribute 'c'
285
     * @return HTMLElement[]
286
     */
287
    public function get(string $selector)
288
    {
289
        $tag = null;
290
        $attr = null;
291
        $val = null;
292
293
        // Define what could be found
294
        $selector = trim($selector);
295
        if ($selector[0] == "#") {
296
            $attr = "id";
297
            $val = mb_substr($selector, 1);
298
        } elseif ($selector[0] == ".") {
299
            $attr = "class";
300
            $val = mb_substr($selector, 1);
301
        } elseif (mb_strpos($selector, "=") !== false) {
302
            list($attr, $val) = explode("=", $selector);
303
        } elseif (mb_strpos($selector, "#") !== false) {
304
            $attr = "id";
305
            list($tag, $val) = explode("#", $selector);
306
        } elseif (mb_strpos($selector, ".") !== false) {
307
            $attr = "class";
308
            list($tag, $val) = explode(".", $selector);
309
        } else {
310
            $tag = $selector;
311
        }
312
313
        return $this->getInternal($this, $tag, $attr, $val);
314
    }
315
316
    /**
317
     * Find and return elements based in $tag, $attr, $val
318
     * The $tag or $attr must be a value
319
     * @see too HTMLElement::get
320
     * @param HTMLElement $element if null $elemnt = $this
321
     * @param string $tag tag of search
322
     * @param string $attr attribute of search
323
     * @param string $val value of attribute search
324
     * @return HTMLElement[]
325
     */
326
    public function getElements($tag, $attr, $val)
327
    {
328
        return $this->getInternal($this, $tag, $attr, $val);
329
    }
330
331
    /**
332
     * Recursive function to found elements
333
     * @param HTMLElement $element Element that will be available
334
     * @param string $tag Tag or null value to compare
335
     * @param string $attr Attribute name or null value to compare
336
     * @param string $val Value of attribute or null value to compare
337
     * @return HTMLElement[]
338
     */
339
    protected function getInternal($element, $tag, $attr, $val)
340
    {
341
        if ($this->match($element, $tag, $attr, $val)) {
342
            $return = [$element];
343
        } else {
344
            $return = [];
345
        }
346
347
        foreach ($element->getContent() as $content) {
348
            if ($content instanceof HTMLElement) {
349
                $return = array_merge($return, $this->getInternal($content, $tag, $attr, $val));
350
            }
351
        }
352
353
        return $return;
354
    }
355
356
    /**
357
     * Return a boolean based on match of the element with $tag, $attr or $val
358
     * @param HTMLElement $element Element that will be available
359
     * @param string $tag Tag or null value to compare
360
     * @param string $attr Attribute name or null value to compare
361
     * @param string $val Value of attribute or null value to compare
362
     * @return boolean - true when satisfy and false otherwise
363
     */
364
    protected function match(HTMLElement $element, $tag, $attr, $val): bool
365
    {
366
        if (!empty($tag)) {
367
            if ($element->getTag() != $tag) {
368
                return false;
369
            }
370
        } elseif (empty($attr)) {
371
            // Only when $tag and $attr is empty
372
            return false;
373
        }
374
375
        if (!empty($attr)) {
376
            $values = $element->getAttribute($attr);
377
            if (count($values) == 0) {
378
                return false;
379
            }
380
381
            if (!empty($val)) {
382
                return in_array($val, $values);
383
            }
384
        }
385
386
        return true;
387
    }
388
389
    public function getRenderHTML(): string
390
    {
391
        return $this->__toString();
392
    }
393
    
394
    /**
395
     * Return the html element code including all children
396
     * @return string (html code)
397
     */
398
    public function __toString()
399
    {
400
        if ($this->renderIfEmpty == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
401
            if (!count($this->content)) {
402
                return '';
403
            }
404
        }
405
406
        $data = [];
407
        if (!empty($this->tag)) {
408
            $data[] = '<' . htmlspecialchars($this->tag);
409
410
            foreach ($this->attributes as $atrib => $value) {
411
                $data[] = ' ' . $atrib . '="' . htmlspecialchars(implode(' ', $value)) . '"';
412
            }
413
            $data[] = '>';
414
        }
415
416
        $contentdata = [];
417
        $emptyfieldset = ($this->tag == 'fieldset'); // avoid rendering fieldset with only a "legend"
418
        foreach ($this->content as $content) {
419
            if ($content instanceof HTMLElement) {
420
                $c = $content->getRenderHTML();
421
                if ($this->tag == 'fieldset' and $content->getTag() != 'legend' and $c) {
422
                    $emptyfieldset = false;
423
                }
424
                $contentdata[] = $c;
425
            } else {
426
                $emptyfieldset = false;
427
                $contentdata[] = $content;
428
            }
429
        }
430
431
        if ($emptyfieldset) {
432
            return '';
433
        } elseif ($contentdata == [] && $this->renderIfEmpty == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
434
            return '';
435
        }
436
437
        $data = array_merge($data, $contentdata);
438
439
        //If dont HTML tags with no closing
440
        if (!empty($this->tag) && !in_array(
441
            $this->tag,
442
            ['img', 'hr', 'br', 'input', 'meta', 'col', 'command', 'link', 'param', 'source', 'embed']
443
        )
444
        ) {
445
            $data[] = '</' . htmlspecialchars($this->tag) . '>';
446
        }
447
448
        return implode("", $data);
449
    }
450
451
    /**
452
     * Clone HTMLElement object and its child
453
     * @return Object HTMLElement
454
     */
455
    public function __clone()
456
    {
457
        $obj = new HTMLElement($this->tag, $this->attributes);
458
        foreach ($this->content as $content) {
459
            if ($content instanceof HTMLElement) {
460
                $obj->addContent(clone $content);
461
            } else {
462
                $obj->addContent($content);
463
            }
464
        }
465
    }
466
467
    /**
468
     * Clear All Attributes
469
     */
470
    public function clearAttributes(): HTMLElement
471
    {
472
        $this->attributes = [];
473
        return $this;
474
    }
475
476
477
    /**
478
     * Clear All Content
479
     */
480
    public function clearContent(): HTMLElement
481
    {
482
        $this->content = [];
483
        return $this;
484
    }
485
}
486