AttributeCollection::target()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * File containing the class {@see \AppUtils\AttributeCollection}.
4
 *
5
 * @package AppUtils
6
 * @subpackage HTML
7
 * @see \AppUtils\AttributeCollection
8
 */
9
10
declare(strict_types=1);
11
12
namespace AppUtils;
13
14
use AppUtils\AttributeCollection\AttributesRenderer;
15
use AppUtils\AttributeCollection\Filtering;
16
use AppUtils\Interfaces\StylableInterface;
17
use AppUtils\Traits\StylableTrait;
18
19
/**
20
 * Utility class used to hold HTML attributes, with
21
 * chainable methods and an easy-to-use API for handling
22
 * and filtering values.
23
 *
24
 * @package AppUtils
25
 * @subpackage HTML
26
 * @author Sebastian Mordziol <[email protected]>
27
 */
28
class AttributeCollection
29
    implements
30
    Interface_Stringable,
31
    Interface_Classable,
32
    StylableInterface
33
{
34
    use Traits_Classable;
35
    use StylableTrait;
36
37
    /**
38
     * @var array<string,string>
39
     */
40
    private array $attributes = array();
41
42
    /**
43
     * @var StyleCollection
44
     */
45
    public StyleCollection $styles;
46
47
    private ?AttributesRenderer $renderer = null;
48
49
    /**
50
     * @var array<string,bool>
51
     */
52
    private array $keepEmpty = array();
53
54
    /**
55
     * @var string[]
56
     */
57
    private array $empty = array();
58
59
    /**
60
     * @param array<string,string|number|bool|NULL|Interface_Stringable|StringBuilder_Interface> $attributes
61
     */
62
    private function __construct(array $attributes)
63
    {
64
        $this->styles = StyleCollection::create();
65
66
        $this->setAttributes($attributes);
67
    }
68
69
    public function getStyles() : StyleCollection
70
    {
71
        return $this->styles;
72
    }
73
74
    /**
75
     * @param array<string,string|number|bool|NULL|Interface_Stringable|StringBuilder_Interface|NULL> $attributes
76
     * @return $this
77
     */
78
    public function setAttributes(array $attributes) : AttributeCollection
79
    {
80
        foreach($attributes as $name => $value)
81
        {
82
            $this->attr($name, $value);
83
        }
84
85
        return $this;
86
    }
87
88
    public function getAttribute(string $name, string $default='') : string
89
    {
90
        return $this->attributes[$name] ?? $default;
91
    }
92
93
    /**
94
     * @param array<string,string|number|bool|NULL|Interface_Stringable|StringBuilder_Interface> $attributes
95
     * @return AttributeCollection
96
     */
97
    public static function create(array $attributes=array()) : AttributeCollection
98
    {
99
        return new AttributeCollection($attributes);
100
    }
101
102
    public function prop(string $name, bool $enabled=true) : AttributeCollection
103
    {
104
        if($enabled)
105
        {
106
            return $this->attr($name, $name);
107
        }
108
109
        return $this->remove($name);
110
    }
111
112
    /**
113
     * @param string $name
114
     * @param string|number|bool|Interface_Stringable|StringBuilder_Interface|NULL $value
115
     * @return $this
116
     */
117
    public function attr(string $name, $value) : AttributeCollection
118
    {
119
        $string = Filtering::value2string($value);
120
121
        if($name === 'class')
122
        {
123
            return $this->addClasses(ConvertHelper::explodeTrim(' ', $string));
124
        }
125
126
        if($name === 'style')
127
        {
128
            $this->styles->parseStylesString($string);
129
            return $this;
130
        }
131
132
        if($string !== '')
133
        {
134
            $this->attributes[$name] = $string;
135
136
            // remove it from the empty stack if it was empty before.
137
            if(in_array($name, $this->empty, true)) {
138
                $this->empty = array_remove_values($this->empty, array($name));
139
            }
140
        }
141
        else
142
        {
143
            unset($this->attributes[$name]);
144
145
            $this->empty[] = $name;
146
        }
147
148
        return $this;
149
    }
150
151
    /**
152
     * Adds an attribute, and escapes double quotes in the value.
153
     *
154
     * @param string $name
155
     * @param string|number|bool|Interface_Stringable|StringBuilder_Interface|NULL $value $value
156
     * @return $this
157
     */
158
    public function attrQuotes(string $name, $value) : AttributeCollection
159
    {
160
        $this->attr($name, $value);
161
162
        if(isset($this->attributes[$name]))
163
        {
164
            $this->attributes[$name] = Filtering::quotes($this->attributes[$name]);
165
        }
166
167
        return $this;
168
    }
169
170
    public function attrURL(string $name, string $url) : AttributeCollection
171
    {
172
        return $this->attr($name, Filtering::URL($url));
173
    }
174
175
    public function remove(string $name) : AttributeCollection
176
    {
177
        if(isset($this->attributes[$name]))
178
        {
179
            unset($this->attributes[$name]);
180
        }
181
182
        return $this;
183
    }
184
185
    public function hasAttribute(string $name) : bool
186
    {
187
        return isset($this->attributes[$name]);
188
    }
189
190
    public function hasAttributes() : bool
191
    {
192
        $attributes = $this->getAttributes();
193
194
        return !empty($attributes);
195
    }
196
197
    private function getRenderer() : AttributesRenderer
198
    {
199
        if(isset($this->renderer))
200
        {
201
            return $this->renderer;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->renderer could return the type null which is incompatible with the type-hinted return AppUtils\AttributeCollection\AttributesRenderer. Consider adding an additional type-check to rule them out.
Loading history...
202
        }
203
204
        $renderer = new AttributesRenderer($this);
205
        $this->renderer = $renderer;
206
        return $renderer;
207
    }
208
209
    /**
210
     * Retrieves the attributes as an associative array
211
     * with name => value pairs.
212
     *
213
     * @return array<string,string>
214
     */
215
    public function getAttributes() : array
216
    {
217
        return $this->getRenderer()->compileAttributes();
218
    }
219
220
    /**
221
     * Like {@see AttributeCollection::getAttributes()}, but
222
     * without the dynamically generated attributes (like
223
     * `class` and `style`). These are just the attributes
224
     * that have been set manually.
225
     *
226
     * @return array<string,string>
227
     */
228
    public function getRawAttributes() : array
229
    {
230
        $attributes = $this->attributes;
231
232
        // Are there any empty attributes to keep?
233
        if(!empty($this->keepEmpty))
234
        {
235
            $names = array_keys($this->keepEmpty);
236
237
            foreach($names as $name) {
238
                if(in_array($name, $this->empty, true)) {
239
                    $attributes[$name] = '';
240
                }
241
            }
242
        }
243
244
        return $attributes;
245
    }
246
247
    public function setKeepIfEmpty(string $name, bool $keep=true) : self
248
    {
249
        if($keep === true)
250
        {
251
            $this->keepEmpty[$name] = true;
252
        }
253
        else if(isset($this->keepEmpty[$name]))
254
        {
255
            unset($this->keepEmpty[$name]);
256
        }
257
258
        return $this;
259
    }
260
261
    /**
262
     * Whether the attribute should be kept even it is empty.
263
     *
264
     * @param string $name
265
     * @return bool
266
     * @see AttributeCollection::setKeepIfEmpty()
267
     */
268
    public function isKeepIfEmpty(string $name) : bool
269
    {
270
        return isset($this->keepEmpty[$name]) && $this->keepEmpty[$name] === true;
271
    }
272
273
    public function render() : string
274
    {
275
        return $this->getRenderer()->render();
276
    }
277
278
    public function __toString()
279
    {
280
        return $this->render();
281
    }
282
283
    // region: Flavors
284
285
    public function name(string $name) : AttributeCollection
286
    {
287
        return $this->attr('name', $name);
288
    }
289
290
    public function id(string $id) : AttributeCollection
291
    {
292
        return $this->attr('id', $id);
293
    }
294
295
    public function href(string $url) : AttributeCollection
296
    {
297
        return $this->attrURL('href', $url);
298
    }
299
300
    public const TARGET_BLANK = '_blank';
301
302
    public function target(string $value=self::TARGET_BLANK) : AttributeCollection
303
    {
304
        return $this->attr('target', $value);
305
    }
306
307
    // endregion
308
}
309