Collection   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
wmc 40
eloc 86
c 4
b 0
f 1
dl 0
loc 240
rs 9.2

8 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 8 2
A renderItems() 0 14 3
A run() 0 5 1
A renderItemContainer() 0 13 3
A renderSecondary() 0 17 6
B renderItem() 0 39 11
A hasHeader() 0 9 4
B renderAvatar() 0 29 10

How to fix   Complexity   

Complex Class

Complex classes like Collection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Collection, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace dmgpage\yii2materialize\widgets;
3
4
use dmgpage\yii2materialize\helpers\Html;
5
use yii\helpers\ArrayHelper;
6
use yii\base\InvalidConfigException;
7
8
/**
9
 * Collections allow you to group list objects together.
10
 *
11
 * ```php
12
 * echo Collection::widget([
13
 *     'items' => [
14
 *         [
15
 *             'label' => '<h5>Best Panda</h5>',
16
 *             'encode' => false,
17
 *             'header' => true
18
 *         ],
19
 *         [
20
 *             'label' => 'Kung Fu Panda',
21
 *             'secondary' => [
22
 *                 'icon' => [
23
 *                     'name' => 'favorite',
24
 *                     'options' =>  ['class' => 'red-text'],
25
 *                 ],
26
 *                 'url' => '#',
27
 *                 'options' => ['target' => '_blank']
28
 *             ]
29
 *         ],
30
 *         [
31
 *             'label' => 'Kung Fu Panda',
32
 *             'secondary' => ['icon' => 'favorite']
33
 *         ],
34
 *         [
35
 *             'label' => 'Kung Fu Panda',
36
 *             'secondary' => ['icon' => 'favorite']
37
 *         ],
38
 *         [
39
 *             'label' => 'Kung Fu Panda',
40
 *             'secondary' => ['icon' => 'favorite']
41
 *         ]
42
 *     ]
43
 * ]);
44
 * ```
45
 * @see https://materializecss.com/collections.html
46
 * @package widgets
47
 */
48
class Collection extends Widget
49
{
50
    /**
51
     * @var array list of items in the collection widget. Each array element represents a single
52
     * row with the following structure:
53
     *
54
     * - avatar: array, optional, options for creating avatar content. Available options are:
55
     *   - icon: string|array, optional, the options for the icon. See [[Html::icon()]]
56
     *   - image: string|array, optional, the options for the avatar image. See [[Html::img()]]
57
     *   - title: string, title for the item avatar. Value will be HTML-encoded.
58
     *   - titleOptions: array the HTML attributes for the title tag.
59
     * - label: string, required, the item label.
60
     * - header: boolean, optional, whether this label should be formatted as header.
61
     * - encode: boolean, optional, whether this label should be HTML-encoded. This param will override
62
     *   global `$this->encodeLabels` param.
63
     * - options: array, optional, the HTML attributes of the item container (LI).
64
     * - url: array|string, optional, the URL for the hyperlink tag. Defaults to "#".
65
     * - linkOptions: array, optional, the HTML attributes of the item's link.
66
     * - active: boolean, optional, whether this item should be active.
67
     * - visible: boolean, optional, whether the item tab header and pane should be visible or not. Defaults to true.
68
     * - secondary: array, optional, options for creating secondary content. Available options are:
69
     *   - icon: string|array, required, the options for the icon. See [[Html::icon()]]
70
     *   - options: array, optional, the HTML attributes of the icon link.
71
     *     for more description.
72
     *   - url: array|string, optional, the URL for the icon. Defaults to "#".
73
     */
74
    public $items = [];
75
76
    /**
77
     * @var boolean whether the labels for items should be HTML-encoded.
78
     */
79
    public $encodeLabels = true;
80
81
    /**
82
     * @var boolean weather format each item as a link
83
     */
84
    public $asLinks = false;
85
86
    /**
87
     * Initializes the widget.
88
     */
89
    public function init()
90
    {
91
        parent::init();
92
93
        Html::addCssClass($this->options, ['widget' => 'collection']);
94
95
        if ($this->hasHeader()) {
96
            Html::addCssClass($this->options, ['header' => 'with-header']);
97
        }
98
    }
99
100
    /**
101
     * Renders the widget.
102
     */
103
    public function run()
104
    {
105
        $this->initializePlugin = true;
106
        $this->registerPlugin('collection');
107
        return $this->renderItems();
108
    }
109
110
    /**
111
     * Renders tab items as specified on [[items]].
112
     *
113
     * @return string the rendering result.
114
     * @throws InvalidConfigException.
115
     */
116
    protected function renderItems()
117
    {
118
        $rows = [];
119
120
        foreach ($this->items as $item) {
121
            $rows[] = $this->renderItem($item);
122
        }
123
124
        $containerTag = $this->asLinks ? 'div' : 'ul';
125
        $html = Html::beginTag($containerTag, $this->options);
126
        $html .= implode("\n", $rows);
127
        $html .= Html::endTag($containerTag);
128
129
        return $html;
130
    }
131
132
    /**
133
     * Renders single collection item as specified on [[items]].
134
     *
135
     * @param array $item single collection element
136
     * @return string the rendering result
137
     * @throws InvalidConfigException.
138
     */
139
    protected function renderItem($item)
140
    {
141
        if (!array_key_exists('label', $item)) {
142
            throw new InvalidConfigException("The 'label' option is required.");
143
        } elseif (ArrayHelper::remove($item, 'visible', true)) {
144
            $itemContent = null;
145
            $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
146
            $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
147
            $isHeader = ArrayHelper::getValue($item, 'header', false);
148
            $avatar = ArrayHelper::remove($item, 'avatar', []);
149
            $defaultUrl = $this->asLinks ? '#' : null;
150
            $url = ArrayHelper::getValue($item, 'url', $defaultUrl);
151
            $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
152
            $options = ArrayHelper::getValue($item, 'options', []);
153
            $isActive = ArrayHelper::getValue($item, 'active', false);
154
            $containerClass = $isHeader ? 'collection-header' : 'collection-item';
155
            $secondaryItem = ArrayHelper::getValue($item, 'secondary', []);
156
            Html::addCssClass($options, ['item' => $containerClass]);
157
158
            // Avatar
159
            if (!empty($avatar) && !$isHeader) {
160
                Html::addCssClass($options, ['avatar' => 'avatar']);
161
                $itemContent .= $this->renderAvatar($avatar);
162
            }
163
164
            // Main content
165
            if (!$this->asLinks && !empty($url)) {
166
                $itemContent .= Html::a($label, $url, $linkOptions);
167
            } else {
168
                $itemContent .= $label;
169
            }
170
171
            // Secondary content
172
            $itemContent .= $this->renderSecondary($secondaryItem, $isHeader);
173
174
            // Item container
175
            $html = $this->renderItemContainer($itemContent, $url, $options, $isActive);
176
177
            return $html;
178
        }
179
    }
180
181
    /**
182
     * Renders single avatar image or icon as specified on [[avatar]].
183
     *
184
     * @param array $avatar single avatar item
185
     * @param bool $isHeader whether this label should be formatted as header
186
     * @return string the rendering result
187
     *
188
     * @throws InvalidConfigException.
189
     * @see https://materializecss.com/collections.html#secondary
190
     */
191
    protected function renderAvatar($avatar)
192
    {
193
        $content = null;
194
        $image = ArrayHelper::getValue($avatar, 'image', []);
195
        $icon = ArrayHelper::getValue($avatar, 'icon', []);
196
        $title = ArrayHelper::getValue($avatar, 'title', null);
197
        $titleOptions = ArrayHelper::getValue($avatar, 'titleOptions', []);
198
199
        // Avatar
200
        if (!empty($image)) {
201
            $url = is_string($image) ? $image : ArrayHelper::remove($image, 'url', '#');
202
            $options = is_array($image) ? ArrayHelper::getValue($image, 'options', []) : [];
203
            $options['class'] = isset($options['class']) ? $options['class'] : 'circle';
204
            $content .= Html::img($url, $options);
205
        } elseif (!empty($icon)) {
206
            $name = is_string($icon) ? $icon : ArrayHelper::remove($icon, 'name', null);
207
            $options = is_array($icon) ? ArrayHelper::getValue($icon, 'options', []) : [];
208
            $options['class'] = isset($options['class']) ? $options['class'] : 'circle';
209
            $content .= Html::icon($name, $options);
210
        }
211
212
        // Title
213
        if (!empty($title)) {
214
            $title = Html::encode($title);
215
            Html::addCssClass($titleOptions, ['title' => 'title']);
216
            $content .= Html::tag('span', $title, $titleOptions);
217
        }
218
219
        return $content;
220
    }
221
222
    /**
223
     * Renders single secondary content item as specified on [[secondary]].
224
     *
225
     * @param array $item single secondary content element
226
     * @param bool $isHeader whether this label should be formatted as header
227
     * @return string the rendering result
228
     *
229
     * @throws InvalidConfigException.
230
     * @see https://materializecss.com/collections.html#secondary
231
     */
232
    protected function renderSecondary($item, $isHeader)
233
    {
234
        $content = null;
235
236
        if (!empty($item) && !$isHeader && !array_key_exists('icon', $item)) {
237
            throw new InvalidConfigException("The 'icon' option is required for secondary content.");
238
        } else if (!empty($item) && !$isHeader) {
239
            $url = ArrayHelper::getValue($item, 'url', '#');
240
            $options = ArrayHelper::getValue($item, 'options', []);
241
242
            Html::addCssClass($options, ['secondary' => 'secondary-content']);
243
244
            $icon = $this->renderIcon($item['icon']);
245
            $content = Html::a($icon, $url, $options);
246
        }
247
248
        return $content;
249
    }
250
251
    /**
252
     * Renders single container item and it's content
253
     *
254
     * @param string $content container content
255
     * @param string $url url in case, if container will be link
256
     * @param array $options array, optional, the HTML attributes of the container tag
257
     * @param bool $isActive whether this item should be active
258
     * @return string the rendering result
259
     */
260
    protected function renderItemContainer($content, $url, $options, $isActive)
261
    {
262
        if ($isActive) {
263
            Html::addCssClass($options, ['active' => 'active']);
264
        }
265
266
        if ($this->asLinks) {
267
            $html = Html::a($content, $url, $options);
268
        } else {
269
            $html = Html::tag('li', $content, $options);
270
        }
271
272
        return $html;
273
    }
274
275
    /**
276
     * Searches for header value in [[items]]
277
     * @return boolean if there's header defined
278
     */
279
    protected function hasHeader()
280
    {
281
        foreach ($this->items as $item) {
282
            if (isset($item['header']) && $item['header'] === true) {
283
                return true;
284
            }
285
        }
286
287
        return false;
288
    }
289
}
290