Completed
Pull Request — master (#127)
by Aydin
01:53
created

SplitItem::buildRows()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 4
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 int|null
23
     */
24
    private $selectedItemIndex;
25
26
    /**
27
     * @var bool
28
     */
29
    private $canBeSelected = true;
30
31
    /**
32
     * @var int
33
     */
34
    private $margin = 2;
35
36
    /**
37
     * @var array
38
     */
39
    private static $blacklistedItems = [
40
        \PhpSchool\CliMenu\MenuItem\AsciiArtItem::class,
41
        \PhpSchool\CliMenu\MenuItem\LineBreakItem::class,
42
        \PhpSchool\CliMenu\MenuItem\SplitItem::class,
43
    ];
44
45
    public function __construct(array $items = [])
46
    {
47
        $this->addItems($items);
48
        $this->setDefaultSelectedItem();
49
    }
50
51
    public function addItem(MenuItemInterface $item) : self
52
    {
53
        foreach (self::$blacklistedItems as $bl) {
54
            if ($item instanceof $bl) {
55
                throw new \InvalidArgumentException("Cannot add a $bl to a SplitItem");
56
            }
57
        }
58
        $this->items[] = $item;
59
        $this->setDefaultSelectedItem();
60
        return $this;
61
    }
62
63
    public function addItems(array $items) : self
64
    {
65
        foreach ($items as $item) {
66
            $this->addItem($item);
67
        }
68
            
69
        return $this;
70
    }
71
72
    public function setItems(array $items) : self
73
    {
74
        $this->items = [];
75
        $this->addItems($items);
76
        return $this;
77
    }
78
79
    /**
80
     * Select default item
81
     */
82
    private function setDefaultSelectedItem() : void
83
    {
84
        foreach ($this->items as $index => $item) {
85
            if ($item->canSelect()) {
86
                $this->canBeSelected = true;
87
                $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|null. 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...
88
                return;
89
            }
90
        }
91
92
        $this->canBeSelected = false;
93
        $this->selectedItemIndex = null;
94
    }
95
96
    /**
97
     * The output text for the item
98
     */
99
    public function getRows(MenuStyle $style, bool $selected = false) : array
100
    {
101
        $numberOfItems = count($this->items);
102
103
        if ($numberOfItems === 0) {
104
            throw new \RuntimeException(sprintf('There should be at least one item added to: %s', __CLASS__));
105
        }
106
        
107
        if (!$selected) {
108
            $this->setDefaultSelectedItem();
109
        }
110
111
        $length = $style->getDisplaysExtra()
112
            ? floor($style->getContentWidth() / $numberOfItems) - (mb_strlen($style->getItemExtra()) + 2)
113
            : floor($style->getContentWidth() / $numberOfItems);
114
        
115
        $length -= $this->margin;
116
        
117
        $missingLength = $style->getContentWidth() % $numberOfItems;
118
        
119
        return $this->buildRows(
120
            array_map(function ($index, $item) use ($selected, $length, $style) {
121
                $isSelected = $selected && $index === $this->selectedItemIndex;
122
                $marker = $item->canSelect()
123
                    ? sprintf('%s ', $style->getMarker($isSelected))
124
                    : '';
125
126
                $itemExtra = '';
127
                if ($style->getDisplaysExtra()) {
128
                    $itemExtra = $item->showsItemExtra()
129
                        ? sprintf('  %s', $style->getItemExtra())
130
                        : sprintf('  %s', str_repeat(' ', mb_strlen($style->getItemExtra())));
131
                }
132
133
                return $this->buildCell(
134
                    explode("\n", StringUtil::wordwrap(sprintf('%s%s', $marker, $item->getText()), $length)),
135
                    $length,
136
                    $style,
137
                    $isSelected,
138
                    $itemExtra
139
                );
140
            }, array_keys($this->items), $this->items),
141
            $style,
142
            $missingLength,
143
            $length
144
        );
145
    }
146
147
    private function buildRows(array $cells, MenuStyle $style, int $missingLength, int $length) : array
148
    {
149
        $extraPadLength = $style->getDisplaysExtra() ? 2 + mb_strlen($style->getItemExtra()) : 0;
150
        
151
        return array_map(
152
            function ($i) use ($cells, $length, $missingLength, $extraPadLength) {
153
                return $this->buildRow($cells, $i, $length, $missingLength, $extraPadLength);
154
            },
155
            range(0, max(array_map('count', $cells)) - 1)
156
        );
157
    }
158
159
    private function buildRow(array $cells, int $index, int $length, int $missingLength, int $extraPadLength) : string
160
    {
161
        return sprintf(
162
            '%s%s',
163
            implode(
164
                '',
165
                array_map(
166
                    function ($cell) use ($index, $length, $extraPadLength) {
167
                        return $cell[$index] ?? str_repeat(' ', $length + $this->margin + $extraPadLength);
168
                    },
169
                    $cells
170
                )
171
            ),
172
            str_repeat(' ', $missingLength)
173
        );
174
    }
175
176
    private function buildCell(
177
        array $content,
178
        int $length,
179
        MenuStyle $style,
180
        bool $isSelected,
181
        string $itemExtra
182
    ) : array {
183
        return array_map(function ($row, $index) use ($length, $style, $isSelected, $itemExtra) {
184
            $invertedColoursSetCode = $isSelected
185
                ? $style->getInvertedColoursSetCode()
186
                : '';
187
            $invertedColoursUnsetCode = $isSelected
188
                ? $style->getInvertedColoursUnsetCode()
189
                : '';
190
191
            return sprintf(
192
                '%s%s%s%s%s%s',
193
                $invertedColoursSetCode,
194
                $row,
195
                str_repeat(' ', $length - mb_strlen($row)),
196
                $index === 0 ? $itemExtra : str_repeat(' ', mb_strlen($itemExtra)),
197
                $invertedColoursUnsetCode,
198
                str_repeat(' ', $this->margin)
199
            );
200
        }, $content, array_keys($content));
201
    }
202
203
    public function setSelectedItemIndex(int $index) : void
204
    {
205
        if (!isset($this->items[$index])) {
206
            throw new \InvalidArgumentException(sprintf('Index: "%s" does not exist', $index));
207
        }
208
        
209
        $this->selectedItemIndex = $index;
210
    }
211
212
    public function getSelectedItemIndex() : ?int
213
    {
214
        return $this->selectedItemIndex;
215
    }
216
217
    public function getSelectedItem() : MenuItemInterface
218
    {
219
        return $this->selectedItemIndex !== null
220
            ? $this->items[$this->selectedItemIndex]
221
            : $this;
222
    }
223
224
    public function getItems() : array
225
    {
226
        return $this->items;
227
    }
228
229
    /**
230
     * Can the item be selected
231
     * In this case, it indicates if at least 1 item inside the SplitItem can be selected
232
     */
233
    public function canSelect() : bool
234
    {
235
        return $this->canBeSelected;
236
    }
237
238
    /**
239
     * Execute the items callable if required
240
     */
241
    public function getSelectAction() : ?callable
242
    {
243
        return null;
244
    }
245
246
    /**
247
     * Whether or not the menu item is showing the menustyle extra value
248
     */
249
    public function showsItemExtra() : bool
250
    {
251
        return false;
252
    }
253
254
    /**
255
     * Enable showing item extra
256
     */
257
    public function showItemExtra() : void
258
    {
259
        //noop
260
    }
261
262
    /**
263
     * Disable showing item extra
264
     */
265
    public function hideItemExtra() : void
266
    {
267
        //noop
268
    }
269
270
    /**
271
     * Nothing to return with SplitItem
272
     */
273
    public function getText() : string
274
    {
275
        throw new \BadMethodCallException(sprintf('Not supported on: %s', __CLASS__));
276
    }
277
}
278