Breadcrumbs   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 58
c 2
b 0
f 0
dl 0
loc 206
ccs 46
cts 46
cp 1
rs 10
wmc 23

8 Methods

Rating   Name   Duplication   Size   Complexity  
A tag() 0 6 1
A itemTemplate() 0 6 1
A items() 0 6 1
A attributes() 0 6 1
A homeItem() 0 12 2
A activeItemTemplate() 0 6 1
B render() 0 30 8
B renderItem() 0 27 8
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Widgets;
6
7
use InvalidArgumentException;
8
use Yiisoft\Html\Html;
9
use Yiisoft\Widget\Widget;
10
11
use function array_key_exists;
12
use function implode;
13
use function is_array;
14
use function is_string;
15
use function strtr;
16
17
/**
18
 * Breadcrumbs displays a list of items indicating the position of the current page in the whole site hierarchy.
19
 *
20
 * For example, breadcrumbs like "Home / Sample Post / Edit" means the user is viewing an edit page for the
21
 * "Sample Post". He can click on "Sample Post" to view that page, or he can click on "Home" to return to the homepage.
22
 *
23
 * To use Breadcrumbs, you need to configure its {@see Breadcrumbs::items()} method,
24
 * which specifies the items to be displayed. For example:
25
 *
26
 * ```php
27
 * // $this is the view object currently being used
28
 * echo Breadcrumbs::widget()
29
 *     -> itemTemplate() => "<li><i>{link}</i></li>\n", // template for all links
30
 *     -> items() => [
31
 *         [
32
 *             'label' => 'Post Category',
33
 *             'url' => 'post-category/view?id=10',
34
 *             'template' => "<li><b>{link}</b></li>\n", // template for this link only
35
 *         ],
36
 *         ['label' => 'Sample Post', 'url' => 'post/edit?id=1'],
37
 *         'Edit',
38
 *     ];
39
 * ```
40
 *
41
 * Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view.
42
 * You can use a view common parameter (e.g. `$this->getCommonParameter('breadcrumbs')`) to configure the items in
43
 * different views. In the layout view, you assign this view parameter to the {@see Breadcrumbs::items()} method
44
 * like the following:
45
 *
46
 * ```php
47
 * // $this is the view object currently being used
48
 * echo Breadcrumbs::widget()->items($this->getCommonParameter('breadcrumbs', []));
49
 * ```
50
 */
51
final class Breadcrumbs extends Widget
52
{
53
    private string $activeItemTemplate = "<li class=\"active\">{link}</li>\n";
54
    private array $attributes = ['class' => 'breadcrumb'];
55
    private array|null $homeItem = ['label' => 'Home', 'url' => '/'];
56
    private array $items = [];
57
    private string $itemTemplate = "<li>{link}</li>\n";
58
    private string $tag = 'ul';
59
60
    /**
61
     * Returns a new instance with the specified active item template.
62
     *
63
     * @param string $value The template used to render each active item in the breadcrumbs.
64
     * The token `{link}` will be replaced with the actual HTML link for each active item.
65
     */
66
    public function activeItemTemplate(string $value): self
67
    {
68
        $new = clone $this;
69 4
        $new->activeItemTemplate = $value;
70
71 4
        return $new;
72 4
    }
73 4
74
    /**
75
     * Returns a new instance with the HTML attributes. The following special options are recognized.
76
     *
77
     * @param array $valuesMap Attribute values indexed by attribute names.
78
     */
79
    public function attributes(array $valuesMap): self
80
    {
81 2
        $new = clone $this;
82
        $new->attributes = $valuesMap;
83 2
84 2
        return $new;
85 2
    }
86
87
    /**
88
     * Returns a new instance with the specified first item in the breadcrumbs (called home link).
89
     *
90
     * If a null is specified, the home item will not be rendered.
91
     *
92
     * @param array|null $value Please refer to {@see items()} on the format.
93
     *
94
     * @throws InvalidArgumentException If an empty array is specified.
95
     */
96
    public function homeItem(?array $value): self
97
    {
98
        if ($value === []) {
99 8
            throw new InvalidArgumentException(
100
                'The home item cannot be an empty array. To disable rendering of the home item, specify null.',
101 8
            );
102 1
        }
103
104
        $new = clone $this;
105
        $new->homeItem = $value;
106
107 7
        return $new;
108 7
    }
109 7
110
    /**
111
     * Returns a new instance with the specified list of items.
112
     *
113
     * @param array $value List of items to appear in the breadcrumbs. If this property is empty, the widget will not
114
     * render anything. Each array element represents a single item in the breadcrumbs with the following structure:
115
     *
116
     * ```php
117
     * [
118
     *     'label' => 'label of the item',  // required
119
     *     'url' => 'url of the item',      // optional
120
     *     'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used
121
     * ]
122
     * ```
123
     *
124
     * If an item is active, you only need to specify its "label", and instead of writing `['label' => $label]`, you may
125
     * simply use `$label`.
126
     *
127
     * Additional array elements for each item will be treated as the HTML attributes for the hyperlink tag.
128
     * For example, the following item specification will generate a hyperlink with CSS class `external`:
129
     *
130
     * ```php
131
     * [
132
     *     'label' => 'demo',
133
     *     'url' => 'http://example.com',
134
     *     'class' => 'external',
135
     * ]
136
     * ```
137
     *
138
     * To disable encode for a specific item, you can set the encode option to false:
139
     *
140
     * ```php
141
     * [
142
     *     'label' => '<strong>Hello!</strong>',
143
     *     'encode' => false,
144
     * ]
145
     * ```
146
     */
147
    public function items(array $value): self
148
    {
149
        $new = clone $this;
150
        $new->items = $value;
151 10
152
        return $new;
153 10
    }
154 10
155 10
    /**
156
     * Returns a new instance with the specified item template.
157
     *
158
     * @param string $value The template used to render each inactive item in the breadcrumbs.
159
     * The token `{link}` will be replaced with the actual HTML link for each inactive item.
160
     */
161
    public function itemTemplate(string $value): self
162
    {
163
        $new = clone $this;
164
        $new->itemTemplate = $value;
165
166
        return $new;
167
    }
168
169
    /**
170
     * Returns a new instance with the specified tag.
171 5
     *
172
     * @param string $value The tag name.
173 5
     */
174 5
    public function tag(string $value): self
175 5
    {
176
        $new = clone $this;
177
        $new->tag = $value;
178
179
        return $new;
180
    }
181
182
    /**
183
     * Renders the widget.
184
     *
185
     * @return string The result of widget execution to be outputted.
186 2
     */
187
    public function render(): string
188 2
    {
189 2
        if ($this->items === []) {
190 2
            return '';
191
        }
192
193
        $items = [];
194
195
        if ($this->homeItem !== null) {
196
            $items[] = $this->renderItem($this->homeItem, $this->itemTemplate);
197
        }
198
199
        foreach ($this->items as $item) {
200
            if (!is_array($item)) {
201 4
                $item = ['label' => $item];
202
            }
203 4
204 4
            if ($item !== []) {
205 4
                $items[] = $this->renderItem(
206
                    $item,
207
                    isset($item['url']) ? $this->itemTemplate : $this->activeItemTemplate
208
                );
209
            }
210
        }
211
212
        $body = implode('', $items);
213
214
        return empty($this->tag)
215 10
            ? $body
216
            : Html::normalTag($this->tag, PHP_EOL . $body, $this->attributes)->encode(false)->render();
217 10
    }
218 1
219
    /**
220
     * Renders a single breadcrumb item.
221 9
     *
222
     * @param array $item The item to be rendered. It must contain the "label" element. The "url" element is optional.
223 9
     * @param string $template The template to be used to render the link. The token "{link}" will be replaced by the
224 4
     * link.
225
     *
226
     * @throws InvalidArgumentException if `$item` does not have "label" element.
227 9
     *
228 9
     * @return string The rendering result.
229 7
     */
230
    private function renderItem(array $item, string $template): string
231
    {
232 9
        if (!array_key_exists('label', $item)) {
233 9
            throw new InvalidArgumentException('The "label" element is required for each item.');
234
        }
235 9
236
        if (!is_string($item['label'])) {
237
            throw new InvalidArgumentException('The "label" element must be a string.');
238
        }
239
240 8
        /** @var bool $encodeLabel */
241
        $encodeLabel = $item['encode'] ?? true;
242 8
        $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
243 2
244 8
        if (isset($item['template']) && is_string($item['template'])) {
245
            $template = $item['template'];
246
        }
247
248
        if (isset($item['url']) && is_string($item['url'])) {
249
            $link = $item['url'];
250
            unset($item['template'], $item['label'], $item['url']);
251
            $link = Html::a($label, $link, $item);
252
        } else {
253
            $link = $label;
254
        }
255
256
        return strtr($template, ['{link}' => $link]);
257
    }
258
}
259