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

SplitItem   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 6
dl 0
loc 249
rs 8.8
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A setDefaultSelectedItem() 0 13 3
A addItem() 0 10 1
A addStaticItem() 0 6 1
A addSubMenu() 0 11 2
A end() 0 18 3
D getRows() 0 67 13
A setSelectedItemIndex() 0 4 1
A getSelectedItemIndex() 0 7 2
A getSelectedItem() 0 4 1
A getItems() 0 4 1
A canSelect() 0 4 1
A getSelectAction() 0 4 1
A showsItemExtra() 0 4 1
A showItemExtra() 0 4 1
A hideItemExtra() 0 4 1
A getText() 0 8 2
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
    public function __construct(CliMenuBuilder $builder, array $items = [])
43
    {
44
        $this->parentBuilder = $builder;
45
        $this->items         = $items;
46
47
        $this->setDefaultSelectedItem();
48
    }
49
50
    /**
51
     * Select default item
52
     */
53
    private function setDefaultSelectedItem()
54
    {
55
        foreach ($this->items as $index => $item) {
56
            if ($item->canSelect()) {
57
                $this->canBeSelected = true;
58
                $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...
59
                return;
60
            }
61
        }
62
63
        $this->canBeSelected = false;
64
        $this->selectedItemIndex = null;
65
    }
66
67
    public function addItem(
68
        string $text,
69
        callable $itemCallable,
70
        bool $showItemExtra = false,
71
        bool $disabled = false
72
    ) : self {
73
        $this->items[] = new SelectableItem($text, $itemCallable, $showItemExtra, $disabled);
74
75
        return $this;
76
    }
77
78
    public function addStaticItem(string $text) : self
79
    {
80
        $this->items[] = new StaticItem($text);
81
82
        return $this;
83
    }
84
85
    public function addSubMenu(string $id, CliMenuBuilder $subMenuBuilder = null) : CliMenuBuilder
86
    {
87
        if (null === $subMenuBuilder) {
88
            $subMenuBuilder = new CliMenuBuilder($this->parentBuilder, $this);
89
        }
90
91
        $this->items[] = $id;
92
        $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...
93
94
        return $subMenuBuilder;
95
    }
96
97
    public function end() : CliMenuBuilder
98
    {
99
        $this->items = array_map(function ($item) {
100
            if (!is_string($item) || empty($this->subMenuBuilders[$item])) {
101
                return $item;
102
            }
103
104
            $subMenuBuilder = $this->subMenuBuilders[$item];
105
            $subMenu = $subMenuBuilder->build();
106
            $this->parentBuilder->injectSubMenu($item, $subMenu);
107
108
            return new MenuMenuItem($item, $subMenu, $subMenuBuilder->isMenuDisabled());
109
        }, $this->items);
110
111
        $this->setDefaultSelectedItem();
112
113
        return $this->parentBuilder;
114
    }
115
116
    /**
117
     * The output text for the item
118
     */
119
    public function getRows(MenuStyle $style, bool $selected = false) : array
120
    {
121
        $numberOfItems = count($this->items);
122
123
        if (!$selected) {
124
            $this->setDefaultSelectedItem();
125
        }
126
127
        $length = $style->getDisplaysExtra()
128
            ? floor(($style->getContentWidth() - mb_strlen($style->getItemExtra()) + 2) / $numberOfItems) - $this->margin
129
            : floor($style->getContentWidth() / $numberOfItems) - $this->margin;
130
        $missingLength = $style->getContentWidth() % $numberOfItems;
131
132
        $lines = 0;
133
        $cells = [];
134
        foreach ($this->items as $index => $item) {
135
            $isSelected = $selected && $index === $this->selectedItemIndex;
136
            $marker = sprintf("%s ", $style->getMarker($isSelected));
137
            $content = StringUtil::wordwrap(
138
                sprintf('%s%s', $marker, $item->getText()),
139
                $length
140
            );
141
            $cell = array_map(function ($row) use ($index, $length, $style, $isSelected) {
142
                $invertedColoursSetCode = $isSelected
143
                    ? $style->getInvertedColoursSetCode()
144
                    : '';
145
                $invertedColoursUnsetCode = $isSelected
146
                    ? $style->getInvertedColoursUnsetCode()
147
                    : '';
148
149
                return sprintf(
150
                    "%s%s%s%s%s",
151
                    $invertedColoursSetCode,
152
                    $row,
153
                    str_repeat(' ', $length - mb_strlen($row)),
154
                    $invertedColoursUnsetCode,
155
                    str_repeat(' ', $this->margin)
156
                );
157
            }, explode("\n", $content));
158
            $lineCount = count($cell);
159
            if ($lineCount > $lines) {
160
                $lines = $lineCount;
161
            }
162
            $cells[] = $cell;
163
        }
164
165
        $rows = [];
166
        for ($i = 0; $i < $lines; $i++) {
167
            $row = "";
168
            if ($i > 0) {
169
                $row .= str_repeat(' ', 2);
170
            }
171
            foreach ($cells as $cell) {
172
                if (isset($cell[$i])) {
173
                    $row .= $cell[$i];
174
                } else {
175
                    $row .= str_repeat(' ', $length);
176
                }
177
            }
178
            if ($missingLength) {
179
                $row .= str_repeat(' ', $missingLength);
180
            }
181
            $rows[] = $row;
182
        }
183
184
        return $rows;
185
    }
186
187
    public function setSelectedItemIndex(int $index) : void
188
    {
189
        $this->selectedItemIndex = $index;
190
    }
191
192
    public function getSelectedItemIndex() : int
193
    {
194
        if ($this->selectedItemIndex === null) {
195
            return 0;
196
        }
197
        return $this->selectedItemIndex;
198
    }
199
200
    public function getSelectedItem() : MenuItem
201
    {
202
        return $this->items[$this->selectedItemIndex];
203
    }
204
205
    public function getItems() : array
206
    {
207
        return $this->items;
208
    }
209
210
    /**
211
     * Can the item be selected
212
     * Not really in this case but that's the trick
213
     */
214
    public function canSelect() : bool
215
    {
216
        return $this->canBeSelected;
217
    }
218
219
    /**
220
     * Execute the items callable if required
221
     */
222
    public function getSelectAction() : ?callable
223
    {
224
        return null;
225
    }
226
227
    /**
228
     * Whether or not the menu item is showing the menustyle extra value
229
     */
230
    public function showsItemExtra() : bool
231
    {
232
        return false;
233
    }
234
235
    /**
236
     * Enable showing item extra
237
     */
238
    public function showItemExtra() : void
239
    {
240
        //noop
241
    }
242
243
    /**
244
     * Disable showing item extra
245
     */
246
    public function hideItemExtra() : void
247
    {
248
        //noop
249
    }
250
251
    /**
252
     * Return the raw string of text
253
     */
254
    public function getText() : string
255
    {
256
        $text = [];
257
        foreach ($this->items as $item) {
258
            $text[] = $item->getText();
259
        }
260
        return explode(' - ', $text);
261
    }
262
}
263