Completed
Pull Request — master (#125)
by
unknown
10:44
created

SplitItem::getRows()   D

Complexity

Conditions 13
Paths 260

Size

Total Lines 67
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 67
rs 4.3252
c 0
b 0
f 0
cc 13
eloc 49
nc 260
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpSchool\CliMenu\MenuItem;
4
5
use Assert\Assertion;
6
use PhpSchool\CliMenu\CliMenu;
7
use PhpSchool\CliMenu\CliMenuBuilder;
8
use PhpSchool\CliMenu\MenuStyle;
9
use PhpSchool\CliMenu\Util\StringUtil;
10
11
/**
12
 * @author Michael Woodward <[email protected]>
13
 */
14
class SplitItem implements MenuItemInterface
15
{
16
    /**
17
     * @var array
18
     */
19
    private $items;
20
21
    /**
22
     * @var CliMenuBuilder
23
     */
24
    private $parentBuilder;
25
26
    /**
27
     * @var int
28
     */
29
    private $selectedItemIndex;
30
31
    /**
32
     * @var bool
33
     */
34
    private $canBeSelected = true;
35
36
    /**
37
     * @var int
38
     */
39
    private $margin = 2;
40
41
    /**
42
     * @var array
43
     */
44
    private $blacklistedItems = [
45
        '\PhpSchool\CliMenu\MenuItem\AsciiArtItem',
46
        '\PhpSchool\CliMenu\MenuItem\LineBreakItem',
47
        '\PhpSchool\CliMenu\MenuItem\SplitItem',
48
    ];
49
50
51
    public function __construct(CliMenuBuilder $builder, array $items = [])
52
    {
53
        $this->parentBuilder = $builder;
54
        $this->items         = $items;
55
56
        $this->setDefaultSelectedItem();
57
    }
58
59
    /**
60
     * Select default item
61
     */
62
    private function setDefaultSelectedItem()
63
    {
64
        foreach ($this->items as $index => $item) {
65
            if ($item instanceof MenuItemInterface && $item->canSelect()) {
66
                $this->canBeSelected = true;
67
                $this->selectedItemIndex = $index;
0 ignored issues
show
Documentation Bug introduced by
It seems like $index can also be of type string. However, the property $selectedItemIndex is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
68
                return;
69
            }
70
        }
71
72
        $this->canBeSelected = false;
73
        $this->selectedItemIndex = null;
74
    }
75
76
    public function addMenuItem(MenuItemInterface $item) : self
77
    {
78
        foreach ($this->blacklistedItems as $bl) {
79
            if ($item instanceof $bl) {
80
                throw new \InvalidArgumentException("Cannot add a $bl to a SplitItem");
81
            }
82
        }
83
84
        $this->items[] = $item;
85
86
        $this->setDefaultSelectedItem();
87
88
        return $this;
89
    }
90
91
    public function addMenuItems(array $items) : self
92
    {
93
        foreach ($items as $item) {
94
            $this->addMenuItem($item);
95
        }
96
97
        return $this;
98
    }
99
100
    public function setItems(array $items) : self
0 ignored issues
show
Unused Code introduced by
The parameter $items is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
101
    {
102
        $this->items = [];
103
        $this->addMenuItems($item);
0 ignored issues
show
Bug introduced by
The variable $item does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
104
105
        return $this;
106
    }
107
108
    public function addItem(
109
        string $text,
110
        callable $itemCallable,
111
        bool $showItemExtra = false,
112
        bool $disabled = false
113
    ) : self {
114
        $this->items[] = new SelectableItem($text, $itemCallable, $showItemExtra, $disabled);
115
116
        return $this;
117
    }
118
119
    public function addStaticItem(string $text) : self
120
    {
121
        $this->items[] = new StaticItem($text);
122
123
        return $this;
124
    }
125
126
    public function addSubMenu(string $id, CliMenuBuilder $subMenuBuilder = null) : CliMenuBuilder
127
    {
128
        if (null === $subMenuBuilder) {
129
            $subMenuBuilder = new CliMenuBuilder($this->parentBuilder, $this);
130
        }
131
132
        $this->items[] = $id;
133
        $this->subMenuBuilders[$id] = $subMenuBuilder;
0 ignored issues
show
Bug introduced by
The property subMenuBuilders does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
134
135
        return $subMenuBuilder;
136
    }
137
138
    public function end() : CliMenuBuilder
139
    {
140
        $this->items = array_map(function ($item) {
141
            if (!is_string($item) || empty($this->subMenuBuilders[$item])) {
142
                return $item;
143
            }
144
145
            $subMenuBuilder = $this->subMenuBuilders[$item];
146
            $subMenu = $subMenuBuilder->build();
147
            $this->parentBuilder->injectSubMenu($item, $subMenu);
148
149
            return new MenuMenuItem($item, $subMenu, $subMenuBuilder->isMenuDisabled());
150
        }, $this->items);
151
152
        $this->setDefaultSelectedItem();
153
154
        return $this->parentBuilder;
155
    }
156
157
    /**
158
     * The output text for the item
159
     */
160
    public function getRows(MenuStyle $style, bool $selected = false) : array
161
    {
162
        $numberOfItems = count($this->items);
163
164
        if (!$selected) {
165
            $this->setDefaultSelectedItem();
166
        }
167
168
        $length = $style->getDisplaysExtra()
169
            ? floor(($style->getContentWidth() - mb_strlen($style->getItemExtra()) + 2) / $numberOfItems) - $this->margin
170
            : floor($style->getContentWidth() / $numberOfItems) - $this->margin;
171
        $missingLength = $style->getContentWidth() % $numberOfItems;
172
173
        $lines = 0;
174
        $cells = [];
175
        foreach ($this->items as $index => $item) {
176
            $isSelected = $selected && $index === $this->selectedItemIndex;
177
            $marker = sprintf("%s ", $style->getMarker($isSelected));
178
            $content = StringUtil::wordwrap(
179
                sprintf('%s%s', $marker, $item->getText()),
180
                $length
181
            );
182
            $cell = array_map(function ($row) use ($index, $length, $style, $isSelected) {
183
                $invertedColoursSetCode = $isSelected
184
                    ? $style->getInvertedColoursSetCode()
185
                    : '';
186
                $invertedColoursUnsetCode = $isSelected
187
                    ? $style->getInvertedColoursUnsetCode()
188
                    : '';
189
190
                return sprintf(
191
                    "%s%s%s%s%s",
192
                    $invertedColoursSetCode,
193
                    $row,
194
                    str_repeat(' ', $length - mb_strlen($row)),
195
                    $invertedColoursUnsetCode,
196
                    str_repeat(' ', $this->margin)
197
                );
198
            }, explode("\n", $content));
199
            $lineCount = count($cell);
200
            if ($lineCount > $lines) {
201
                $lines = $lineCount;
202
            }
203
            $cells[] = $cell;
204
        }
205
206
        $rows = [];
207
        for ($i = 0; $i < $lines; $i++) {
208
            $row = "";
209
            if ($i > 0) {
210
                $row .= str_repeat(' ', 2);
211
            }
212
            foreach ($cells as $cell) {
213
                if (isset($cell[$i])) {
214
                    $row .= $cell[$i];
215
                } else {
216
                    $row .= str_repeat(' ', $length);
217
                }
218
            }
219
            if ($missingLength) {
220
                $row .= str_repeat(' ', $missingLength);
221
            }
222
            $rows[] = $row;
223
        }
224
225
        return $rows;
226
    }
227
228
    public function setSelectedItemIndex(int $index) : void
229
    {
230
        $this->selectedItemIndex = $index;
231
    }
232
233
    public function getSelectedItemIndex() : int
234
    {
235
        if ($this->selectedItemIndex === null) {
236
            return 0;
237
        }
238
        return $this->selectedItemIndex;
239
    }
240
241
    public function getSelectedItem() : MenuItem
242
    {
243
        return $this->items[$this->selectedItemIndex];
244
    }
245
246
    public function getItems() : array
247
    {
248
        return $this->items;
249
    }
250
251
    /**
252
     * Can the item be selected
253
     * Not really in this case but that's the trick
254
     */
255
    public function canSelect() : bool
256
    {
257
        return $this->canBeSelected;
258
    }
259
260
    /**
261
     * Execute the items callable if required
262
     */
263
    public function getSelectAction() : ?callable
264
    {
265
        return null;
266
    }
267
268
    /**
269
     * Whether or not the menu item is showing the menustyle extra value
270
     */
271
    public function showsItemExtra() : bool
272
    {
273
        return false;
274
    }
275
276
    /**
277
     * Enable showing item extra
278
     */
279
    public function showItemExtra() : void
280
    {
281
        //noop
282
    }
283
284
    /**
285
     * Disable showing item extra
286
     */
287
    public function hideItemExtra() : void
288
    {
289
        //noop
290
    }
291
292
    /**
293
     * Return the raw string of text
294
     */
295
    public function getText() : string
296
    {
297
        $text = [];
298
        foreach ($this->items as $item) {
299
            $text[] = $item->getText();
300
        }
301
        return explode(' - ', $text);
302
    }
303
}
304