Completed
Push — master ( 57186d...a3ca22 )
by Sebastian
01:30
created

BaseElement::style()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
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)
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
     * Conditionally 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 \Closure $callback
308
     *
309
     * @return mixed
310
     */
311
    public function if(bool $condition, \Closure $callback)
312
    {
313
        return $condition ? $callback($this) : $this;
314
    }
315
316
    /**
317
     * Conditionally transform the element. Note that since elements are
318
     * immutable, you'll need to return a new instance from the callback.
319
     *
320
     * @param bool $condition
321
     * @param \Closure $callback
322
     *
323
     * @return mixed
324
     */
325
    public function unless(bool $condition, \Closure $callback)
326
    {
327
        return $this->if(! $condition, $callback);
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
    /**
390
     * Dynamically handle calls to the class.
391
     * Check for methods finishing by If or fallback to Macroable.
392
     *
393
     * @param  string  $name
394
     * @param  array   $arguments
395
     * @return mixed
396
     *
397
     * @throws BadMethodCallException
398
     */
399
    public function __call($name, $arguments)
400
    {
401
        if (ends_with($name, $conditions = ['If', 'Unless'])) {
402
            foreach ($conditions as $condition) {
403
                if (! method_exists($this, $method = str_replace($condition, '', $name))) {
404
                    continue;
405
                }
406
407
                return $this->callConditionalMethod($condition, $method, $arguments);
408
            }
409
        }
410
411
        return $this->__macro_call($name, $arguments);
412
    }
413
414
    protected function callConditionalMethod($type, $method, array $arguments)
415
    {
416
        $condition = (bool) array_shift($arguments);
417
        $callback = function () use ($method, $arguments) {
418
            return $this->{$method}(...$arguments);
419
        };
420
421
        switch ($type) {
422
            case 'If':
423
                return $this->if($condition, $callback);
424
            case 'Unless':
425
                return $this->unless($condition, $callback);
426
            default:
427
                return $this;
428
        }
429
    }
430
431
    public function __clone()
432
    {
433
        $this->attributes = clone $this->attributes;
434
        $this->children = clone $this->children;
435
    }
436
437
    public function __toString(): string
438
    {
439
        return $this->render();
440
    }
441
442
    public function toHtml(): string
443
    {
444
        return $this->render();
445
    }
446
447
    protected function parseChildren($children, $mapper = null): Collection
448
    {
449
        if ($children instanceof HtmlElement) {
450
            $children = [$children];
451
        }
452
453
        $children = Collection::make($children);
454
455
        if ($mapper) {
456
            $children = $children->map($mapper);
457
        }
458
459
        $this->guardAgainstInvalidChildren($children);
460
461
        return $children;
462
    }
463
464
    protected function guardAgainstInvalidChildren(Collection $children)
465
    {
466
        foreach ($children as $child) {
467
            if ((! $child instanceof HtmlElement) && (! is_string($child)) && (! is_null($child))) {
468
                throw InvalidChild::childMustBeAnHtmlElementOrAString();
469
            }
470
        }
471
    }
472
}
473