HtmlElement::tag()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Lagdo\UiBuilder\Component;
4
5
use Lagdo\UiBuilder\Builder\Engine\Engine;
6
use Lagdo\UiBuilder\Component\Html\Element;
7
8
use function array_filter;
9
use function array_keys;
10
use function htmlspecialchars;
11
use function implode;
12
use function is_bool;
13
use function is_numeric;
14
use function is_string;
15
use function sprintf;
16
use function trim;
17
18
/**
19
 * The HTML tags finally generated for a given component
20
 *
21
 * @method static setId(string $id, bool $escape = true)
22
 * @method static setClass(string $class, bool $escape = true)
23
 * @method static setFor(string $for, bool $escape = true)
24
 * @method static setName(string $name, bool $escape = true)
25
 * @method static setValue(string $value, bool $escape = true)
26
 * @method static setType(string $type, bool $escape = true)
27
 * @method static setTitle(string $type, bool $escape = true)
28
 * @method static setStyle(string $type, bool $escape = true)
29
 */
30
class HtmlElement extends Element
31
{
32
    /**
33
     * @var bool
34
     */
35
    private $isShort = false;
36
37
    /**
38
     * @var bool
39
     */
40
    private $isOpened = false;
41
42
    /**
43
     * @var array
44
     */
45
    private $attributes = [];
46
47
    /**
48
     * @var array
49
     */
50
    private $escapes = [];
51
52
    /**
53
     * @var array
54
     */
55
    private $baseClasses = []; // The component classes.
56
57
    /**
58
     * @var array
59
     */
60
    private $classes = []; // The additional classes.
61
62
    /**
63
     * @var array<Element>
64
     */
65
    private $children = [];
66
67
    /**
68
     * @param Engine $engine
69
     * @param string $tag
70
     * @param array $attributes
71
     */
72
    public function __construct(private Engine $engine, private string $tag, array $attributes = [])
73
    {
74
        $this->setAttributes($attributes);
75
    }
76
77
    /**
78
     * @param string $method
79
     * @param array $arguments
80
     *
81
     * @return static
82
     */
83
    public function __call(string $method, array $arguments): static
84
    {
85
        $this->engine->callElementHelper($this, $method, $arguments);
86
        return $this;
87
    }
88
89
    /**
90
     * @return string
91
     */
92
    public function tag(): string
93
    {
94
        return $this->tag;
95
    }
96
97
    /**
98
     * @param string $tag
99
     *
100
     * @return void
101
     */
102
    public function setTag(string $tag): void
103
    {
104
        $this->tag = $tag;
105
    }
106
107
    /**
108
     * @param array<Element> $children
109
     *
110
     * @return void
111
     */
112
    public function setChildren(array $children): void
113
    {
114
        $this->children = $children;
115
    }
116
117
    /**
118
     * @param array<Element> $children
119
     *
120
     * @return void
121
     */
122
    public function addChildren(array $children): void
123
    {
124
        $this->children = [...$this->children, ...$children];
0 ignored issues
show
Documentation Bug introduced by
It seems like array($this->children, $children) of type array<integer,Lagdo\UiBu...mponent\Html\Element[]> is incompatible with the declared type Lagdo\UiBuilder\Component\Html\Element[] of property $children.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
125
    }
126
127
    /**
128
     * @param Element $child
129
     *
130
     * @return void
131
     */
132
    public function addChild(Element $child): void
133
    {
134
        $this->children[] = $child;
135
    }
136
137
    /**
138
     * @param string $class
139
     *
140
     * @return void
141
     */
142
    public function addBaseClass(string $class): void
143
    {
144
        $this->baseClasses[] = trim($class);
145
    }
146
147
    /**
148
     * @param int $index
149
     * @param string $class
150
     *
151
     * @return void
152
     */
153
    public function setBaseClass(int $index, string $class): void
154
    {
155
       $this->baseClasses[$index] = trim($class);
156
    }
157
158
    /**
159
     * @param string $class
160
     *
161
     * @return void
162
     */
163
    public function addClass(string $class): void
164
    {
165
        $this->classes[trim($class)] = true;
166
    }
167
168
    /**
169
     * @param string $class
170
     *
171
     * @return void
172
     */
173
    public function setClass(string $class): void
174
    {
175
        // Actually appends the class.
176
        $this->addClass($class);
177
    }
178
179
    /**
180
     * @param string $class
181
     *
182
     * @return void
183
     */
184
    public function removeClass(string $class): void
185
    {
186
        $this->classes[trim($class)] = false;
187
    }
188
189
    /**
190
     * @param string $class
191
     *
192
     * @return bool
193
     */
194
    public function hasClass(string $class): bool
195
    {
196
        return ($this->classes[trim($class)] ?? false) === true;
197
    }
198
199
    /**
200
     * @param bool $isShort
201
     *
202
     * @return void
203
     */
204
    public function setShort($isShort): void
205
    {
206
        $this->isShort = $isShort;
207
    }
208
209
    /**
210
     * @param bool $isOpened
211
     *
212
     * @return void
213
     */
214
    public function setOpened($isOpened): void
215
    {
216
        $this->isOpened = $isOpened;
217
    }
218
219
    /**
220
     * @param string $name
221
     * @param string|bool $value
222
     * @param bool $escape
223
     *
224
     * @return static
225
     */
226
    public function setAttribute(string $name, string|bool $value = true, bool $escape = true): void
227
    {
228
        $this->attributes[$name] = $value;
229
        $this->escapes[$name] = $escape;
230
    }
231
232
    /**
233
     * @param array $attributes
234
     * @param bool $escape
235
     *
236
     * @return void
237
     */
238
    public function setAttributes(array $attributes, bool $escape = true): void
239
    {
240
        foreach ($attributes as $name => $value) {
241
            switch (true) {
242
            case is_numeric($name):
243
                $this->setAttribute($value);
244
                break;
245
            case is_string($value):
246
            case is_bool($value):
247
                $this->setAttribute($name, $value, $escape);
248
                break;
249
            default: // Any other values are ignored.
250
            }
251
        }
252
    }
253
254
    /**
255
     * @param string $name
256
     *
257
     * @return bool
258
     */
259
    public function hasAttribute(string $name): bool
260
    {
261
        return isset($this->attributes[$name]) && $this->attributes[$name] !== false;
262
    }
263
264
    /**
265
     * @param string $name
266
     *
267
     * @return string|bool
268
     */
269
    public function getAttribute(string $name): string|bool
270
    {
271
        return $this->attributes[$name] ?? false;
272
    }
273
274
    /**
275
     * @param string $name
276
     *
277
     * @return void
278
     */
279
    public function removeAttribute(string $name): void
280
    {
281
        $this->attributes[$name] = false;
282
    }
283
284
    /**
285
     * @return string
286
     */
287
    private function renderAttributes(): string
288
    {
289
        // Merge the classes.
290
        $classes = array_keys(array_filter($this->classes, fn($active) => $active === true));
291
        $classes = [...$this->baseClasses, ...$classes];
292
        if (isset($this->attributes['class'])) {
293
            $classes[] = $this->attributes['class'];
294
        }
295
        if (($class = trim(implode(' ', $classes))) !== '') {
296
            $this->attributes['class'] = $class;
297
        }
298
299
        $attributes = [];
300
        foreach ($this->attributes as $name => $value) {
301
            // Attributes with false as value are ignored.
302
            if ($value !== false) {
303
                $name = $this->escape($name);
304
                if ($value === true) {
305
                    $attributes[] = $name;
306
                    continue;
307
                }
308
309
                if (($this->escapes[$name] ?? true) === true) {
310
                    $value = $this->escape($value);
311
                }
312
                $attributes[] = "$name=\"$value\"";
313
            }
314
        }
315
316
        return !$attributes ? '' : ' ' . implode(' ', $attributes);
317
    }
318
319
    /**
320
     * @return string
321
     */
322
    private function renderShort(): string
323
    {
324
        return sprintf('<%s%s />', $this->tag, $this->renderAttributes());
325
    }
326
327
    /**
328
     * @return string
329
     */
330
    private function renderOpened(): string
331
    {
332
        return sprintf('<%s%s>', $this->tag, $this->renderAttributes());
333
    }
334
335
    /**
336
     * @return string
337
     */
338
    private function renderTag(): string
339
    {
340
        $children = implode('', $this->children);
341
        return sprintf('%s%s</%s>', $this->renderOpened(), $children, $this->tag);
342
    }
343
344
    /**
345
     * @param $value
346
     *
347
     * @return string
348
     */
349
    private function escape($value): string
350
    {
351
        return htmlspecialchars($value, ENT_COMPAT);
352
    }
353
354
    /**
355
     * @inheritDoc
356
     */
357
    protected function render(): string
358
    {
359
        return match(true) {
360
            $this->isShort => $this->renderShort(),
361
            $this->isOpened => $this->renderOpened(),
362
            default => $this->renderTag(),
363
        };
364
    }
365
}
366