Completed
Push — master ( 67e0ac...639778 )
by Sebastian
01:30
created

BaseElement::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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|null $value
41
     *
42
     * @return static
43
     */
44
    public function attribute($attribute, $value = null)
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|null $value
57
     *
58
     * @return static
59
     */
60
    public function attributeIf($condition, $attribute, $value = null)
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($attributes)
73
    {
74
        $element = clone $this;
75
76
        $element->attributes->setAttributes($attributes);
77
78
        return $element;
79
    }
80
81
    /**
82
     * @param string $attribute
83
     *
84
     * @return static
85
     */
86
    public function forgetAttribute($attribute)
87
    {
88
        $element = clone $this;
89
90
        $element->attributes->forgetAttribute($attribute);
91
92
        return $element;
93
    }
94
95
    /**
96
     * @param string $attribute
97
     * @param mixed $fallback
98
     *
99
     * @return mixed
100
     */
101
    public function getAttribute($attribute, $fallback = null)
102
    {
103
        return $this->attributes->getAttribute($attribute, $fallback);
104
    }
105
106
    /**
107
     * @param string $attribute
108
     *
109
     * @return bool
110
     */
111
    public function hasAttribute($attribute)
112
    {
113
        return $this->attributes->hasAttribute($attribute);
114
    }
115
116
    /**
117
     * @param iterable|string $class
118
     *
119
     * @return static
120
     */
121
    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...
122
    {
123
        return $this->addClass($class);
124
    }
125
126
    /**
127
     * Alias for `class`.
128
     *
129
     * @param iterable|string $class
130
     *
131
     * @return static
132
     */
133
    public function addClass($class)
134
    {
135
        $element = clone $this;
136
137
        $element->attributes->addClass($class);
138
139
        return $element;
140
    }
141
142
    /**
143
     * @param string $id
144
     *
145
     * @return static
146
     */
147
    public function id($id)
148
    {
149
        return $this->attribute('id', $id);
150
    }
151
152
    /**
153
     * @param array|string|null $style
154
     *
155
     * @return static
156
     */
157
    public function style($style)
158
    {
159
        if (is_array($style)) {
160
            $style = implode('; ', array_map(function ($value, $attribute) {
161
                return "{$attribute}: {$value}";
162
            }, $style, array_keys($style)));
163
        }
164
165
        return $this->attribute('style', $style);
166
    }
167
168
    /**
169
     * @param string $name
170
     * @param string $value
171
     *
172
     * @return static
173
     */
174
    public function data($name, $value)
175
    {
176
        return $this->attribute("data-{$name}", $value);
177
    }
178
179
    /**
180
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
181
     * @param callable|null $mapper
182
     *
183
     * @return static
184
     */
185 View Code Duplication
    public function addChildren($children, $mapper = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
186
    {
187
        if (is_null($children)) {
188
            return $this;
189
        }
190
191
        $children = $this->parseChildren($children, $mapper);
192
193
        $element = clone $this;
194
195
        $element->children = $element->children->merge($children);
196
197
        return $element;
198
    }
199
200
    /**
201
     * Alias for `addChildren`.
202
     *
203
     * @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...
204
     * @param callable|null $mapper
205
     *
206
     * @return static
207
     */
208
    public function addChild($child, $mapper = null)
209
    {
210
        return $this->addChildren($child, $mapper);
211
    }
212
213
    /**
214
     * Alias for `addChildren`.
215
     *
216
     * @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...
217
     * @param callable|null $mapper
218
     *
219
     * @return static
220
     */
221
    public function child($child, $mapper = null)
222
    {
223
        return $this->addChildren($child, $mapper);
224
    }
225
226
    /**
227
     * Alias for `addChildren`.
228
     *
229
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
230
     * @param callable|null $mapper
231
     *
232
     * @return static
233
     */
234
    public function children($children, $mapper = null)
235
    {
236
        return $this->addChildren($children, $mapper);
237
    }
238
239
    /**
240
     * Replace all children with an array of elements.
241
     *
242
     * @param \Spatie\Html\HtmlElement[] $children
243
     * @param callable|null $mapper
244
     *
245
     * @return static
246
     */
247
    public function setChildren($children, $mapper = null)
248
    {
249
        $element = clone $this;
250
251
        $element->children = new Collection();
252
253
        return $element->addChildren($children, $mapper);
0 ignored issues
show
Documentation introduced by
$children is of type array<integer,object<Spatie\Html\HtmlElement>>, but the function expects a object<Spatie\Html\HtmlE...tie\Html\iterable>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
254
    }
255
256
    /**
257
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
258
     * @param callable|null $mapper
259
     *
260
     * @return static
261
     */
262 View Code Duplication
    public function prependChildren($children, $mapper = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
263
    {
264
        $children = $this->parseChildren($children, $mapper);
265
266
        $element = clone $this;
267
268
        $element->children = $children->merge($element->children);
269
270
        return $element;
271
    }
272
273
    /**
274
     * Alias for `prependChildren`.
275
     *
276
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
277
     * @param callable|null $mapper
278
     *
279
     * @return static
280
     */
281
    public function prependChild($children, $mapper = null)
282
    {
283
        return $this->prependChildren($children, $mapper);
284
    }
285
286
    /**
287
     * @param string|null $text
288
     *
289
     * @return static
290
     */
291
    public function text($text)
292
    {
293
        return $this->html(htmlentities($text, ENT_QUOTES, 'UTF-8', false));
294
    }
295
296
    /**
297
     * @param string|null $html
298
     *
299
     * @return static
300
     */
301
    public function html($html)
302
    {
303
        if ($this->isVoidElement()) {
304
            throw new InvalidHtml("Can't set inner contents on `{$this->tag}` because it's a void element");
305
        }
306
307
        $element = clone $this;
308
309
        $element->children = new Collection([$html]);
310
311
        return $element;
312
    }
313
314
    /**
315
     * Condintionally transform the element. Note that since elements are
316
     * immutable, you'll need to return a new instance from the callback.
317
     *
318
     * @param bool $condition
319
     * @param callable $callback
320
     */
321
    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...
322
    {
323
        if ($condition) {
324
            return $callback($this);
325
        }
326
327
        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...
328
    }
329
330
    /**
331
     * @return \Illuminate\Contracts\Support\Htmlable
332
     */
333
    public function open()
334
    {
335
        $tag = $this->attributes->isEmpty()
336
            ? '<'.$this->tag.'>'
337
            : "<{$this->tag} {$this->attributes->render()}>";
338
339
        $children = $this->children->map(function ($child): string {
340
            if ($child instanceof HtmlElement) {
341
                return $child->render();
342
            }
343
344
            if (is_null($child)) {
345
                return '';
346
            }
347
348
            if (is_string($child)) {
349
                return $child;
350
            }
351
352
            throw InvalidChild::childMustBeAnHtmlElementOrAString();
353
        })->implode('');
354
355
        return new HtmlString($tag.$children);
356
    }
357
358
    /**
359
     * @return \Illuminate\Contracts\Support\Htmlable
360
     */
361
    public function close()
362
    {
363
        return new HtmlString(
364
            $this->isVoidElement()
365
                ? ''
366
                : "</{$this->tag}>"
367
        );
368
    }
369
370
    /**
371
     * @return \Illuminate\Contracts\Support\Htmlable
372
     */
373
    public function render()
374
    {
375
        return new HtmlString(
376
            $this->open().$this->close()
377
        );
378
    }
379
380
    public function isVoidElement(): bool
381
    {
382
        return in_array($this->tag, [
383
            'area', 'base', 'br', 'col', 'embed', 'hr',
384
            'img', 'input', 'keygen', 'link', 'menuitem',
385
            'meta', 'param', 'source', 'track', 'wbr',
386
        ]);
387
    }
388
389
    public function __clone()
390
    {
391
        $this->attributes = clone $this->attributes;
392
        $this->children = clone $this->children;
393
    }
394
395
    public function __toString(): string
396
    {
397
        return $this->render();
398
    }
399
400
    public function toHtml(): string
401
    {
402
        return $this->render();
403
    }
404
405
    protected function parseChildren($children, $mapper = null): Collection
406
    {
407
        if ($children instanceof HtmlElement) {
408
            $children = [$children];
409
        }
410
411
        $children = Collection::make($children);
412
413
        if ($mapper) {
414
            $children = $children->map($mapper);
415
        }
416
417
        $children = $children->filter();
418
419
        $this->guardAgainstInvalidChildren($children);
420
421
        return $children;
422
    }
423
424
    protected function guardAgainstInvalidChildren(Collection $children)
425
    {
426
        foreach ($children as $child) {
427
            if ((! $child instanceof HtmlElement) && (! is_string($child))) {
428
                throw InvalidChild::childMustBeAnHtmlElementOrAString();
429
            }
430
        }
431
    }
432
}
433