Passed
Pull Request — master (#51)
by Wilmer
11:42
created

Breadcrumbs::autoIdPrefix()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Bulma;
6
7
use InvalidArgumentException;
8
use Yiisoft\Html\Html;
9
use Yiisoft\Widget\Widget;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Yiisoft\Yii\Bulma\Widget. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
11
use function array_key_exists;
12
use function implode;
13
use function is_array;
14
use function strtr;
15
16
/**
17
 * The Bulma breadcrumb is a simple navigation component.
18
 *
19
 * ```php
20
 * echo Breadcrumbs::widget()->items([
21
 *     ['label' => 'Info'],
22
 *     ['label' => 'Contacts'],
23
 * ]);
24
 * ```
25
 *
26
 * @link https://bulma.io/documentation/components/breadcrumb/
27
 */
28
final class Breadcrumbs extends Widget
29
{
30
    private string $autoIdPrefix = 'w';
31
    private array $attributes = [];
32
    private string $activeItemTemplate = "<li class=\"is-active\"><a aria-current=\"page\">{link}</a></li>\n";
33
    private bool $encode = false;
34
    private ?array $homeItem = ['label' => 'Home', 'url' => '/'];
35
    private array $items = [];
36
    private array $itemsAttributes = [];
37
    private string $itemTemplate = "<li>{link}</li>\n";
38
39
    /**
40
     * Defines a string value that labels the current element.
41
     *
42
     * @param string $value The value of the aria-label attribute.
43
     *
44
     * @return static
45
     *
46
     * @link https://www.w3.org/TR/wai-aria/#aria-label
47
     */
48 12
    public function ariaLabel(string $value): self
49
    {
50 12
        $new = clone $this;
51 12
        $new->attributes['aria-label'] = $value;
52 12
        return $new;
53
    }
54
55
    /**
56
     * Returns a new instance with the specified active item template.
57
     *
58
     * @param string $value The template used to render each active item in the breadcrumbs.
59
     * The token `{link}` will be replaced with the actual HTML link for each active item.
60
     *
61
     * @return self
62
     */
63 2
    public function activeItemTemplate(string $value): self
64
    {
65 2
        $new = clone $this;
66 2
        $new->activeItemTemplate = $value;
67 2
        return $new;
68
    }
69
70
    /**
71
     * Returns a new instance with the specified attributes.
72
     *
73
     * @param array $value The HTML attributes for the widget container nav tag.
74
     *
75
     * @return self
76
     *
77
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
78
     */
79 3
    public function attributes(array $value): self
80
    {
81 3
        $new = clone $this;
82 3
        $new->attributes = $value;
83 3
        return $new;
84
    }
85
86
    /**
87
     * Returns a new instance with the specified prefix to the automatically generated widget IDs.
88
     *
89
     * @param string $value The prefix to the automatically generated widget IDs.
90
     *
91
     * @return static
92
     *
93
     * {@see getId()}
94
     */
95 1
    public function autoIdPrefix(string $value): self
96
    {
97 1
        $new = clone $this;
98 1
        $new->autoIdPrefix = $value;
99 1
        return $new;
100
    }
101
102
    /**
103
     * Set encode to true to encode the output.
104
     *
105
     * @param bool $value whether to encode the output.
106
     *
107
     * @return $this
108
     */
109 1
    public function encode(bool $value): self
110
    {
111 1
        $new = clone $this;
112 1
        $new->encode = $value;
113 1
        return $new;
114
    }
115
116
    /**
117
     * Returns a new instance with the specified first item in the breadcrumbs (called home link).
118
     *
119
     * If a null is specified, the home item will not be rendered.
120
     *
121
     * @param array|null $value Please refer to {@see items()} on the format.
122
     *
123
     * @throws InvalidArgumentException If an empty array is specified.
124
     *
125
     * @return self
126
     */
127 10
    public function homeItem(?array $value): self
128
    {
129 10
        if ($value === []) {
130 1
            throw new InvalidArgumentException(
131 1
                'The home item cannot be an empty array. To disable rendering of the home item, specify null.',
132
            );
133
        }
134
135 9
        $new = clone $this;
136 9
        $new->homeItem = $value;
137 9
        return $new;
138
    }
139
140
    /**
141
     * Returns a new instance with the specified ID of the widget.
142
     *
143
     * @param string $value The ID of the widget.
144
     *
145
     * @return static
146
     */
147 13
    public function id(string $value): self
148
    {
149 13
        $new = clone $this;
150 13
        $new->attributes['id'] = $value;
151 13
        return $new;
152
    }
153
154
    /**
155
     * Returns a new instance with the specified list of items.
156
     *
157
     * @param array $value List of items to appear in the breadcrumbs. If this property is empty, the widget will not
158
     * render anything. Each array element represents a single item in the breadcrumbs with the following structure:
159
     *
160
     * ```php
161
     * [
162
     *     'label' => 'label of the link',  // required
163
     *     'url' => 'url of the link',      // optional, will be processed by Url::to()
164
     *     'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used
165
     * ]
166
     * ```
167
     *
168
     * @return self
169
     */
170 14
    public function items(array $value): self
171
    {
172 14
        $new = clone $this;
173 14
        $new->items = $value;
174 14
        return $new;
175
    }
176
177
    /**
178
     * Returns a new instance with the specified item template.
179
     *
180
     * @param array $value The HTML attributes for the item's widget.
181
     *
182
     * @return self
183
     *
184
     * {@see Html::renderTagAttributes()} for details on how attributes are being rendered.
185
     */
186 2
    public function itemsAttributes(array $value): self
187
    {
188 2
        $new = clone $this;
189 2
        $new->itemsAttributes = $value;
190 2
        return $new;
191
    }
192
193
    /**
194
     * Returns a new instance with the specified item template.
195
     *
196
     * @param string $value The template used to render each inactive item in the breadcrumbs.
197
     * The token `{link}` will be replaced with the actual HTML link for each inactive item.
198
     *
199
     * @return self
200
     */
201 2
    public function itemTemplate(string $value): self
202
    {
203 2
        $new = clone $this;
204 2
        $new->itemTemplate = $value;
205 2
        return $new;
206
    }
207
208 13
    protected function run(): string
209
    {
210 13
        $new = clone $this;
211
212 13
        if (empty($new->items)) {
213 1
            return '';
214
        }
215
216 12
        $new = $new->setId();
217 12
        $new = $new->ariaLabel('breadcrumbs');
218 12
        Html::addCssClass($new->attributes, 'breadcrumb');
219
220
        return
221 12
            Html::openTag('nav', $new->attributes) . "\n" .
222 12
            Html::openTag('ul', $new->itemsAttributes) . "\n" .
223 12
            implode('', $new->renderItems()) .
224 11
            Html::closeTag('ul') . "\n" .
225 11
            Html::closeTag('nav');
226
    }
227
228 12
    private function renderIcon(?string $icon, array $iconAttributes): string
229
    {
230 12
        $html = '';
231
232 12
        if ($icon !== null) {
233 1
            $html = Html::openTag('span', $iconAttributes) .
234 1
                Html::tag('i', '', ['class' => $icon, 'aria-hidden' => 'true']) .
235 1
                Html::closeTag('span');
236
        }
237
238 12
        return $html;
239
    }
240
241
    /**
242
     * Renders a single breadcrumb item.
243
     *
244
     * @param array $item The item to be rendered. It must contain the "label" element. The "url" element is optional.
245
     * @param string $template The template to be used to render the link. The token "{link}" will be replaced by the
246
     * link.
247
     *
248
     * @throws InvalidArgumentException If `$item` does not have "label" element.
249
     *
250
     * @return string The rendering result.
251
     */
252 12
    private function renderItem(array $item, string $template): string
253
    {
254 12
        if (!array_key_exists('label', $item)) {
255 1
            throw new InvalidArgumentException('The "label" element is required for each link.');
256
        }
257
258
        /** @var bool */
259 12
        $encode = $item['encode'] ?? $this->encode;
260 12
        unset($item['encode']);
261
        /** @var string|null */
262 12
        $icon = $item['icon'] ?? null;
263 12
        unset($item['icon']);
264
        /** @var array */
265 12
        $iconAttributes = $item['iconAttributes'] ?? [];
266 12
        unset($item['iconAttributes']);
267
        /** @var string */
268 12
        $template = $item['template'] ?? $template;
269 12
        unset($item['template']);
270
        /** @var string|null */
271 12
        $url = $item['url'] ?? null;
272 12
        unset($item['url']);
273 12
        $icon = $this->renderIcon($icon, $iconAttributes);
274
        /** @var string */
275 12
        $label = $item['label'];
276 12
        unset($item['label']);
277
278 12
        if ($icon !== '') {
279 1
            $label = $icon . '<span>' . $label . '</span>';
280
        }
281
282 12
        $link = $url !== null ? Html::a($label, $url, $item)->encode($encode)->render() : $label;
283
284 12
        return strtr($template, ['{link}' => $link, '{label}' => $label, '{icon}' => $icon]);
285
    }
286
287
    /**
288
     * @psalm-return string[]
289
     */
290 12
    private function renderItems(): array
291
    {
292 12
        $items = [];
293
294 12
        if ($this->homeItem !== null) {
295 11
            $items[] = $this->renderItem($this->homeItem, $this->itemTemplate);
296
        }
297
298
        /** @psalm-var string[]|string $item */
299 12
        foreach ($this->items as $item) {
300 12
            if (!is_array($item)) {
301 1
                $item = ['label' => $item];
302
            }
303
304 12
            $items[] = $this->renderItem($item, isset($item['url']) ? $this->itemTemplate : $this->activeItemTemplate);
305
        }
306
307 11
        return $items;
308
    }
309
310
    /**
311
     * Set id of the widget.
312
     *
313
     * @return static
314
     */
315 12
    private function setId(): self
316
    {
317 12
        $new = clone $this;
318
319 12
        $id = Html::generateId($new->autoIdPrefix) . '-breadcrumbs';
320
321 12
        if (!array_key_exists('id', $new->attributes)) {
322 12
            $new = $new->id($id);
323
        }
324
325 12
        return $new;
326
    }
327
}
328