Completed
Push — master ( ff5e96...b5e929 )
by Sebastian
01:37
created

BaseElement::style()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 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 \Spatie\Html\HtmlElement|string|iterable|null $children
170
     * @param callable|null $mapper
171
     *
172
     * @return static
173
     */
174 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...
175
    {
176
        if (is_null($children)) {
177
            return $this;
178
        }
179
180
        $children = $this->parseChildren($children, $mapper);
181
182
        $element = clone $this;
183
184
        $element->children = $element->children->merge($children);
185
186
        return $element;
187
    }
188
189
    /**
190
     * Alias for `addChildren`.
191
     *
192
     * @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...
193
     * @param callable|null $mapper
194
     *
195
     * @return static
196
     */
197
    public function addChild($child, $mapper = null)
198
    {
199
        return $this->addChildren($child, $mapper);
200
    }
201
202
    /**
203
     * Alias for `addChildren`.
204
     *
205
     * @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...
206
     * @param callable|null $mapper
207
     *
208
     * @return static
209
     */
210
    public function child($child, $mapper = null)
211
    {
212
        return $this->addChildren($child, $mapper);
213
    }
214
215
    /**
216
     * Alias for `addChildren`.
217
     *
218
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
219
     * @param callable|null $mapper
220
     *
221
     * @return static
222
     */
223
    public function children($children, $mapper = null)
224
    {
225
        return $this->addChildren($children, $mapper);
226
    }
227
228
    /**
229
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
230
     * @param callable|null $mapper
231
     *
232
     * @return static
233
     */
234 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...
235
    {
236
        $children = $this->parseChildren($children, $mapper);
237
238
        $element = clone $this;
239
240
        $element->children = $children->merge($element->children);
241
242
        return $element;
243
    }
244
245
    /**
246
     * Alias for `prependChildren`.
247
     *
248
     * @param \Spatie\Html\HtmlElement|string|iterable|null $children
249
     * @param callable|null $mapper
250
     *
251
     * @return static
252
     */
253
    public function prependChild($children, $mapper = null)
254
    {
255
        return $this->prependChildren($children, $mapper);
256
    }
257
258
    /**
259
     * @param string|null $text
260
     *
261
     * @return static
262
     */
263
    public function text($text)
264
    {
265
        return $this->html(htmlentities($text, ENT_QUOTES, 'UTF-8', false));
266
    }
267
268
    /**
269
     * @param string|null $html
270
     *
271
     * @return static
272
     */
273
    public function html($html)
274
    {
275
        if ($this->isVoidElement()) {
276
            throw new InvalidHtml("Can't set inner contents on `{$this->tag}` because it's a void element");
277
        }
278
279
        $element = clone $this;
280
281
        $element->children = new Collection([$html]);
282
283
        return $element;
284
    }
285
286
    /**
287
     * Condintionally transform the element. Note that since elements are
288
     * immutable, you'll need to return a new instance from the callback.
289
     *
290
     * @param bool $condition
291
     * @param callable $callback
292
     */
293
    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...
294
    {
295
        if ($condition) {
296
            return $callback($this);
297
        }
298
299
        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...
300
    }
301
302
    /**
303
     * @return \Illuminate\Contracts\Support\Htmlable
304
     */
305
    public function open()
306
    {
307
        $tag = $this->attributes->isEmpty()
308
            ? '<'.$this->tag.'>'
309
            : "<{$this->tag} {$this->attributes->render()}>";
310
311
        $children = $this->children->map(function ($child): string {
312
            if ($child instanceof HtmlElement) {
313
                return $child->render();
314
            }
315
316
            if (is_null($child)) {
317
                return '';
318
            }
319
320
            if (is_string($child)) {
321
                return $child;
322
            }
323
324
            throw InvalidChild::childMustBeAnHtmlElementOrAString();
325
        })->implode('');
326
327
        return new HtmlString($tag.$children);
328
    }
329
330
    /**
331
     * @return \Illuminate\Contracts\Support\Htmlable
332
     */
333
    public function close()
334
    {
335
        return new HtmlString(
336
            $this->isVoidElement()
337
                ? ''
338
                : "</{$this->tag}>"
339
        );
340
    }
341
342
    /**
343
     * @return \Illuminate\Contracts\Support\Htmlable
344
     */
345
    public function render()
346
    {
347
        return new HtmlString(
348
            $this->open().$this->close()
349
        );
350
    }
351
352
    public function isVoidElement(): bool
353
    {
354
        return in_array($this->tag, [
355
            'area', 'base', 'br', 'col', 'embed', 'hr',
356
            'img', 'input', 'keygen', 'link', 'menuitem',
357
            'meta', 'param', 'source', 'track', 'wbr',
358
        ]);
359
    }
360
361
    public function __clone()
362
    {
363
        $this->attributes = clone $this->attributes;
364
        $this->children = clone $this->children;
365
    }
366
367
    public function __toString(): string
368
    {
369
        return $this->render();
370
    }
371
372
    public function toHtml(): string
373
    {
374
        return $this->render();
375
    }
376
377
    protected function parseChildren($children, $mapper = null): Collection
378
    {
379
        if ($children instanceof HtmlElement) {
380
            $children = [$children];
381
        }
382
383
        $children = Collection::make($children);
384
385
        if ($mapper) {
386
            $children = $children->map($mapper);
387
        }
388
389
        $children = $children->filter();
390
391
        $this->guardAgainstInvalidChildren($children);
392
393
        return $children;
394
    }
395
396
    protected function guardAgainstInvalidChildren(Collection $children)
397
    {
398
        foreach ($children as $child) {
399
            if ((! $child instanceof HtmlElement) && (! is_string($child))) {
400
                throw InvalidChild::childMustBeAnHtmlElementOrAString();
401
            }
402
        }
403
    }
404
}
405