Completed
Pull Request — master (#14)
by Sebastian
03:03
created

BaseElement::attributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
1
<?php
2
3
namespace Spatie\Html;
4
5
use Illuminate\Support\Collection;
6
use Illuminate\Support\HtmlString;
7
use Spatie\Html\Exceptions\MissingTag;
8
use Spatie\Html\Exceptions\InvalidHtml;
9
use Spatie\Html\Exceptions\InvalidChild;
10
use Illuminate\Contracts\Support\Htmlable;
11
12
abstract class BaseElement implements Htmlable, HtmlElement
13
{
14
    /** @var string */
15
    protected $tag;
16
17
    /** @var \Spatie\Html\Attributes */
18
    protected $attributes;
19
20
    /** @var \Illuminate\Support\Collection */
21
    protected $children;
22
23
    public function __construct()
24
    {
25
        if (empty($this->tag)) {
26
            throw MissingTag::onClass(static::class);
27
        }
28
29
        $this->attributes = new Attributes();
30
        $this->children = new Collection();
31
    }
32
33
    public static function create()
34
    {
35
        return new static();
36
    }
37
38
    /**
39
     * @param string $attribute
40
     * @param string $value
41
     *
42
     * @return static
43
     */
44
    public function attribute(string $attribute, ?string $value = '')
45
    {
46
        $element = clone $this;
47
48
        $element->attributes->setAttribute($attribute, (string) $value);
49
50
        return $element;
51
    }
52
53
    /**
54
     * @param bool $condition
55
     * @param string $attribute
56
     * @param string $value
57
     *
58
     * @return static
59
     */
60
    public function attributeIf(bool $condition, string $attribute, ?string $value = '')
61
    {
62
        return $condition ?
63
            $this->attribute($attribute, $value) :
64
            $this;
65
    }
66
67
    /**
68
     * @param iterable $attributes
69
     *
70
     * @return static
71
     */
72
    public function attributes(iterable $attributes)
73
    {
74
        $element = clone $this;
75
76
        $element->attributes->setAttributes($attributes);
77
78
        return $element;
79
    }
80
81
    /**
82
     * @param string $attribute
83
     * @param string $value
0 ignored issues
show
Bug introduced by
There is no parameter named $value. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
84
     *
85
     * @return static
86
     */
87
    public function forgetAttribute(string $attribute)
88
    {
89
        $element = clone $this;
90
91
        $element->attributes->forgetAttribute($attribute);
92
93
        return $element;
94
    }
95
96
    /**
97
     * @param string $attribute
98
     * @param mixed $fallback
99
     *
100
     * @return mixed
101
     */
102
    public function getAttribute(string $attribute, ?string $fallback = '')
103
    {
104
        return $this->attributes->getAttribute($attribute, $fallback);
105
    }
106
107
    /**
108
     * @param string $attribute
109
     *
110
     * @return bool
111
     */
112
    public function hasAttribute(string $attribute): bool
113
    {
114
        return $this->attributes->hasAttribute($attribute);
115
    }
116
117
    /**
118
     * @param iterable|string $class
119
     *
120
     * @return static
121
     */
122
    public function class($class)
0 ignored issues
show
Coding Style introduced by
Possible parse error: non-abstract method defined as abstract
Loading history...
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
123
    {
124
        return $this->addClass($class);
125
    }
126
127
    /**
128
     * Alias for `class`.
129
     *
130
     * @param iterable|string $class
131
     *
132
     * @return static
133
     */
134
    public function addClass($class)
135
    {
136
        $element = clone $this;
137
138
        $element->attributes->addClass($class);
139
140
        return $element;
141
    }
142
143
    /**
144
     * @param string $id
145
     *
146
     * @return static
147
     */
148
    public function id(string $id)
149
    {
150
        return $this->attribute('id', $id);
151
    }
152
153
    /**
154
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
155
     * @param ?callable $mapper
0 ignored issues
show
Documentation introduced by
The doc-type ?callable could not be parsed: Unknown type name "?callable" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
156
     *
157
     * @return static
158
     */
159
    public function addChildren($children, callable $mapper = null)
160
    {
161
        if (is_null($children)) {
162
            return $this;
163
        }
164
165
        $children = $this->parseChildren($children, $mapper);
166
167
        $element = clone $this;
168
169
        $element->children = $element->children->merge($children);
170
171
        return $element;
172
    }
173
174
    /**
175
     * Alias for `addChildren`.
176
     *
177
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
0 ignored issues
show
Bug introduced by
There is no parameter named $children. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
178
     * @param ?callable $mapper
0 ignored issues
show
Documentation introduced by
The doc-type ?callable could not be parsed: Unknown type name "?callable" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
179
     *
180
     * @return static
181
     */
182
    public function addChild($child, callable $mapper = null)
183
    {
184
        return $this->addChildren($child, $mapper);
185
    }
186
187
    /**
188
     * Alias for `addChildren`.
189
     *
190
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
0 ignored issues
show
Bug introduced by
There is no parameter named $children. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
191
     * @param ?callable $mapper
0 ignored issues
show
Documentation introduced by
The doc-type ?callable could not be parsed: Unknown type name "?callable" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
192
     *
193
     * @return static
194
     */
195
    public function child($child, callable $mapper = null)
196
    {
197
        return $this->addChildren($child, $mapper);
198
    }
199
200
    /**
201
     * Alias for `addChildren`.
202
     *
203
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
204
     * @param ?callable $mapper
0 ignored issues
show
Documentation introduced by
The doc-type ?callable could not be parsed: Unknown type name "?callable" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
205
     *
206
     * @return static
207
     */
208
    public function children($children, callable $mapper = null)
209
    {
210
        return $this->addChildren($children, $mapper);
211
    }
212
213
    /**
214
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
215
     * @param ?callable $mapper
0 ignored issues
show
Documentation introduced by
The doc-type ?callable could not be parsed: Unknown type name "?callable" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
216
     *
217
     * @return static
218
     */
219
    public function prependChildren($children, callable $mapper = null)
220
    {
221
        $children = $this->parseChildren($children, $mapper);
222
223
        $element = clone $this;
224
225
        $element->children = $children->merge($element->children);
226
227
        return $element;
228
    }
229
230
    /**
231
     * Alias for `prependChildren`.
232
     *
233
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
234
     * @param ?callable $mapper
0 ignored issues
show
Documentation introduced by
The doc-type ?callable could not be parsed: Unknown type name "?callable" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
235
     *
236
     * @return static
237
     */
238
    public function prependChild($children, callable $mapper = null)
239
    {
240
        return $this->prependChildren($children, $mapper);
241
    }
242
243
    /**
244
     * @param string $text
245
     *
246
     * @return static
247
     */
248
    public function text(?string $text)
249
    {
250
        return $this->html(htmlentities($text, ENT_QUOTES, 'UTF-8', false));
251
    }
252
253
    /**
254
     * @param string $html
255
     *
256
     * @return static
257
     */
258
    public function html(?string $html)
259
    {
260
        if ($this->isVoidElement()) {
261
            throw new InvalidHtml("Can't set inner contents on `{$this->tag}` because it's a void element");
262
        }
263
264
        $element = clone $this;
265
266
        $element->children = new Collection([$html]);
267
268
        return $element;
269
    }
270
271
    /**
272
     * Condintionally transform the element. Note that since elements are
273
     * immutable, you'll need to return a new instance from the callback.
274
     *
275
     * @param bool $condition
276
     * @param callable $callback
277
     */
278
    public function if(bool $condition, callable $callback)
0 ignored issues
show
Coding Style introduced by
Possible parse error: non-abstract method defined as abstract
Loading history...
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
279
    {
280
        if ($condition) {
281
            return $callback($this);
282
        }
283
284
        return $this;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $this.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
285
    }
286
287
    public function open(): Htmlable
288
    {
289
        $tag = $this->attributes->isEmpty()
290
            ? '<'.$this->tag.'>'
291
            : "<{$this->tag} {$this->attributes->render()}>";
292
293
        $children = $this->children->map(function ($child): string {
294
            if ($child instanceof HtmlElement) {
295
                return $child->render();
296
            }
297
298
            if (is_null($child)) {
299
                return '';
300
            }
301
302
            if (is_string($child)) {
303
                return $child;
304
            }
305
306
            throw InvalidChild::childMustBeAnHtmlElementOrAString();
307
        })->implode('');
308
309
        return new HtmlString($tag.$children);
310
    }
311
312
    public function close(): Htmlable
313
    {
314
        return new HtmlString(
315
            $this->isVoidElement()
316
                ? ''
317
                : "</{$this->tag}>"
318
        );
319
    }
320
321
    public function render(): Htmlable
322
    {
323
        return new HtmlString(
324
            $this->open().$this->close()
325
        );
326
    }
327
328
    public function isVoidElement(): bool
329
    {
330
        return in_array($this->tag, [
331
            'area', 'base', 'br', 'col', 'embed', 'hr',
332
            'img', 'input', 'keygen', 'link', 'menuitem',
333
            'meta', 'param', 'source', 'track', 'wbr',
334
        ]);
335
    }
336
337
    public function __clone()
338
    {
339
        $this->attributes = clone $this->attributes;
340
        $this->children = clone $this->children;
341
    }
342
343
    public function __toString(): string
344
    {
345
        return $this->render();
346
    }
347
348
    public function toHtml(): string
349
    {
350
        return $this->render();
351
    }
352
353
    protected function parseChildren($children, callable $mapper = null): Collection
354
    {
355
        if ($children instanceof HtmlElement) {
356
            $children = [$children];
357
        }
358
359
        $children = Collection::make($children);
360
361
        if ($mapper) {
362
            $children = $children->map($mapper);
363
        }
364
365
        $children = $children->filter();
366
367
        $this->guardAgainstInvalidChildren($children);
368
369
        return $children;
370
    }
371
372
    protected function guardAgainstInvalidChildren(Collection $children)
373
    {
374
        foreach ($children as $child) {
375
            if ((! $child instanceof HtmlElement) && (! is_string($child))) {
376
                throw InvalidChild::childMustBeAnHtmlElementOrAString();
377
            }
378
        }
379
    }
380
}
381