Completed
Push — master ( 66f49a...94f063 )
by Sebastian
06:38
created

BaseElement::forgetAttribute()   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 BadMethodCallException;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\HtmlString;
8
use Spatie\Html\Exceptions\MissingTag;
9
use Spatie\Html\Exceptions\InvalidHtml;
10
use Illuminate\Support\Traits\Macroable;
11
use Spatie\Html\Exceptions\InvalidChild;
12
use Illuminate\Contracts\Support\Htmlable;
13
14
abstract class BaseElement implements Htmlable, HtmlElement
15
{
16
    use Macroable {
17
        __call as __macro_call;
18
    }
19
20
    /** @var string */
21
    protected $tag;
22
23
    /** @var \Spatie\Html\Attributes */
24
    protected $attributes;
25
26
    /** @var \Illuminate\Support\Collection */
27
    protected $children;
28
29
    public function __construct()
30
    {
31
        if (empty($this->tag)) {
32
            throw MissingTag::onClass(static::class);
33
        }
34
35
        $this->attributes = new Attributes();
36
        $this->children = new Collection();
37
    }
38
39
    public static function create()
40
    {
41
        return new static();
42
    }
43
44
    /**
45
     * @param string $attribute
46
     * @param string|null $value
47
     *
48
     * @return static
49
     */
50
    public function attribute($attribute, $value = null)
51
    {
52
        $element = clone $this;
53
54
        $element->attributes->setAttribute($attribute, (string) $value);
55
56
        return $element;
57
    }
58
59
    /**
60
     * @param iterable $attributes
61
     *
62
     * @return static
63
     */
64
    public function attributes($attributes)
65
    {
66
        $element = clone $this;
67
68
        $element->attributes->setAttributes($attributes);
69
70
        return $element;
71
    }
72
73
    /**
74
     * @param string $attribute
75
     *
76
     * @return static
77
     */
78
    public function forgetAttribute($attribute)
79
    {
80
        $element = clone $this;
81
82
        $element->attributes->forgetAttribute($attribute);
83
84
        return $element;
85
    }
86
87
    /**
88
     * @param string $attribute
89
     * @param mixed $fallback
90
     *
91
     * @return mixed
92
     */
93
    public function getAttribute($attribute, $fallback = null)
94
    {
95
        return $this->attributes->getAttribute($attribute, $fallback);
96
    }
97
98
    /**
99
     * @param string $attribute
100
     *
101
     * @return bool
102
     */
103
    public function hasAttribute($attribute)
104
    {
105
        return $this->attributes->hasAttribute($attribute);
106
    }
107
108
    /**
109
     * @param iterable|string $class
110
     *
111
     * @return static
112
     */
113
    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...
114
    {
115
        return $this->addClass($class);
116
    }
117
118
    /**
119
     * Alias for `class`.
120
     *
121
     * @param iterable|string $class
122
     *
123
     * @return static
124
     */
125
    public function addClass($class)
126
    {
127
        $element = clone $this;
128
129
        $element->attributes->addClass($class);
130
131
        return $element;
132
    }
133
134
    /**
135
     * @param string $id
136
     *
137
     * @return static
138
     */
139
    public function id($id)
140
    {
141
        return $this->attribute('id', $id);
142
    }
143
144
    /**
145
     * @param array|string|null $style
146
     *
147
     * @return static
148
     */
149
    public function style($style)
150
    {
151
        if (is_array($style)) {
152
            $style = implode('; ', array_map(function ($value, $attribute) {
153
                return "{$attribute}: {$value}";
154
            }, $style, array_keys($style)));
155
        }
156
157
        return $this->attribute('style', $style);
158
    }
159
160
    /**
161
     * @param string $name
162
     * @param string $value
163
     *
164
     * @return static
165
     */
166
    public function data($name, $value)
167
    {
168
        return $this->attribute("data-{$name}", $value);
169
    }
170
171
    /**
172
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
173
     * @param callable|null $mapper
174
     *
175
     * @return static
176
     */
177 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...
178
    {
179
        if (is_null($children)) {
180
            return $this;
181
        }
182
183
        $children = $this->parseChildren($children, $mapper);
184
185
        $element = clone $this;
186
187
        $element->children = $element->children->merge($children);
188
189
        return $element;
190
    }
191
192
    /**
193
     * Alias for `addChildren`.
194
     *
195
     * @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...
196
     * @param callable|null $mapper
197
     *
198
     * @return static
199
     */
200
    public function addChild($child, $mapper = null)
201
    {
202
        return $this->addChildren($child, $mapper);
203
    }
204
205
    /**
206
     * Alias for `addChildren`.
207
     *
208
     * @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...
209
     * @param callable|null $mapper
210
     *
211
     * @return static
212
     */
213
    public function child($child, $mapper = null)
214
    {
215
        return $this->addChildren($child, $mapper);
216
    }
217
218
    /**
219
     * Alias for `addChildren`.
220
     *
221
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
222
     * @param callable|null $mapper
223
     *
224
     * @return static
225
     */
226
    public function children($children, $mapper = null)
227
    {
228
        return $this->addChildren($children, $mapper);
229
    }
230
231
    /**
232
     * Replace all children with an array of elements.
233
     *
234
     * @param \Spatie\Html\HtmlElement[] $children
235
     * @param callable|null $mapper
236
     *
237
     * @return static
238
     */
239
    public function setChildren($children, $mapper = null)
240
    {
241
        $element = clone $this;
242
243
        $element->children = new Collection();
244
245
        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...
246
    }
247
248
    /**
249
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
250
     * @param callable|null $mapper
251
     *
252
     * @return static
253
     */
254 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...
255
    {
256
        $children = $this->parseChildren($children, $mapper);
257
258
        $element = clone $this;
259
260
        $element->children = $children->merge($element->children);
261
262
        return $element;
263
    }
264
265
    /**
266
     * Alias for `prependChildren`.
267
     *
268
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
269
     * @param callable|null $mapper
270
     *
271
     * @return static
272
     */
273
    public function prependChild($children, $mapper = null)
274
    {
275
        return $this->prependChildren($children, $mapper);
276
    }
277
278
    /**
279
     * @param string|null $text
280
     *
281
     * @return static
282
     */
283
    public function text($text)
284
    {
285
        return $this->html(htmlentities($text, ENT_QUOTES, 'UTF-8', false));
286
    }
287
288
    /**
289
     * @param string|null $html
290
     *
291
     * @return static
292
     */
293
    public function html($html)
294
    {
295
        if ($this->isVoidElement()) {
296
            throw new InvalidHtml("Can't set inner contents on `{$this->tag}` because it's a void element");
297
        }
298
299
        return $this->setChildren($html);
0 ignored issues
show
Documentation introduced by
$html is of type string|null, but the function expects a array<integer,object<Spatie\Html\HtmlElement>>.

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...
300
    }
301
302
    /**
303
     * Condintionally transform the element. Note that since elements are
304
     * immutable, you'll need to return a new instance from the callback.
305
     *
306
     * @param bool $condition
307
     * @param callable $callback
308
     */
309
    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...
310
    {
311
        if ($condition) {
312
            return $callback($this);
313
        }
314
315
        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...
316
    }
317
318
    /**
319
     * @return \Illuminate\Contracts\Support\Htmlable
320
     */
321
    public function open()
322
    {
323
        $tag = $this->attributes->isEmpty()
324
            ? '<'.$this->tag.'>'
325
            : "<{$this->tag} {$this->attributes->render()}>";
326
327
        $children = $this->children->map(function ($child): string {
328
            if ($child instanceof HtmlElement) {
329
                return $child->render();
330
            }
331
332
            if (is_null($child)) {
333
                return '';
334
            }
335
336
            if (is_string($child)) {
337
                return $child;
338
            }
339
340
            throw InvalidChild::childMustBeAnHtmlElementOrAString();
341
        })->implode('');
342
343
        return new HtmlString($tag.$children);
344
    }
345
346
    /**
347
     * @return \Illuminate\Contracts\Support\Htmlable
348
     */
349
    public function close()
350
    {
351
        return new HtmlString(
352
            $this->isVoidElement()
353
                ? ''
354
                : "</{$this->tag}>"
355
        );
356
    }
357
358
    /**
359
     * @return \Illuminate\Contracts\Support\Htmlable
360
     */
361
    public function render()
362
    {
363
        return new HtmlString(
364
            $this->open().$this->close()
365
        );
366
    }
367
368
    public function isVoidElement(): bool
369
    {
370
        return in_array($this->tag, [
371
            'area', 'base', 'br', 'col', 'embed', 'hr',
372
            'img', 'input', 'keygen', 'link', 'menuitem',
373
            'meta', 'param', 'source', 'track', 'wbr',
374
        ]);
375
    }
376
377
    /**
378
     * Dynamically handle calls to the class.
379
     * Check for methods finishing by If or fallback to Macroable.
380
     *
381
     * @param  string  $method
0 ignored issues
show
Bug introduced by
There is no parameter named $method. 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...
382
     * @param  array   $parameters
0 ignored issues
show
Bug introduced by
There is no parameter named $parameters. 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...
383
     * @return mixed
384
     *
385
     * @throws BadMethodCallException
386
     */
387
    public function __call($name, $arguments)
388
    {
389
        if (ends_with($name, 'If')) {
390
            $name = str_replace('If', '', $name);
391
            if (! method_exists($this, $name)) {
392
                throw new BadMethodCallException("$name is not a valid method for this class");
393
            }
394
395
            $condition = (bool) array_shift($arguments);
396
397
            return $condition ?
398
                $this->{$name}(...$arguments) :
399
                $this;
400
        }
401
402
        return $this->__macro_call($name, $arguments);
403
    }
404
405
    public function __clone()
406
    {
407
        $this->attributes = clone $this->attributes;
408
        $this->children = clone $this->children;
409
    }
410
411
    public function __toString(): string
412
    {
413
        return $this->render();
414
    }
415
416
    public function toHtml(): string
417
    {
418
        return $this->render();
419
    }
420
421
    protected function parseChildren($children, $mapper = null): Collection
422
    {
423
        if ($children instanceof HtmlElement) {
424
            $children = [$children];
425
        }
426
427
        $children = Collection::make($children);
428
429
        if ($mapper) {
430
            $children = $children->map($mapper);
431
        }
432
433
        $this->guardAgainstInvalidChildren($children);
434
435
        return $children;
436
    }
437
438
    protected function guardAgainstInvalidChildren(Collection $children)
439
    {
440
        foreach ($children as $child) {
441
            if ((! $child instanceof HtmlElement) && (! is_string($child)) && (! is_null($child))) {
442
                throw InvalidChild::childMustBeAnHtmlElementOrAString();
443
            }
444
        }
445
    }
446
}
447