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

SplitItem::setDefaultSelectedItem()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 0
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        = floor($style->getContentWidth() / $numberOfItems) - $this->margin;
112
        $missingLength = $style->getContentWidth() % $numberOfItems;
113
        
114
        return $this->buildRows(
115
            array_map(function ($index, $item) use ($selected, $length, $style) {
116
                $isSelected = $selected && $index === $this->selectedItemIndex;
117
                $marker = $item->canSelect()
118
                    ? sprintf('%s ', $style->getMarker($isSelected))
119
                    : '';
120
121
                return $this->buildCell(
122
                    explode("\n", StringUtil::wordwrap(sprintf('%s%s', $marker, $item->getText()), $length)),
123
                    $length,
124
                    $style,
125
                    $isSelected
126
                );
127
            }, array_keys($this->items), $this->items),
128
            $missingLength,
129
            $length
130
        );
131
    }
132
133
    private function buildRows(array $cells, int $missingLength, int $length) : array
134
    {
135
        return array_map(
136
            function ($i) use ($cells, $length, $missingLength) {
137
                return $this->buildRow($cells, $i, $length, $missingLength);
138
            },
139
            range(0, max(array_map('count', $cells)) - 1)
140
        );
141
    }
142
143
    private function buildRow(array $cells, int $index, int $length, int $missingLength) : string
144
    {
145
        return sprintf(
146
            '%s%s',
147
            implode(
148
                '',
149
                array_map(
150
                    function ($cell) use ($index, $length) {
151
                        return $cell[$index] ?? str_repeat(' ', $length + $this->margin);
152
                    },
153
                    $cells
154
                )
155
            ),
156
            str_repeat(' ', $missingLength)
157
        );
158
    }
159
160
    private function buildCell(array $content, int $length, MenuStyle $style, bool $isSelected) : array
161
    {
162
        return array_map(function ($row) use ($length, $style, $isSelected) {
163
            $invertedColoursSetCode = $isSelected
164
                ? $style->getInvertedColoursSetCode()
165
                : '';
166
            $invertedColoursUnsetCode = $isSelected
167
                ? $style->getInvertedColoursUnsetCode()
168
                : '';
169
170
            return sprintf(
171
                '%s%s%s%s%s',
172
                $invertedColoursSetCode,
173
                $row,
174
                str_repeat(' ', $length - mb_strlen($row)),
175
                $invertedColoursUnsetCode,
176
                str_repeat(' ', $this->margin)
177
            );
178
        }, $content);
179
    }
180
181
    public function setSelectedItemIndex(int $index) : void
182
    {
183
        if (!isset($this->items[$index])) {
184
            throw new \InvalidArgumentException(sprintf('Index: "%s" does not exist', $index));
185
        }
186
        
187
        $this->selectedItemIndex = $index;
188
    }
189
190
    public function getSelectedItemIndex() : ?int
191
    {
192
        return $this->selectedItemIndex;
193
    }
194
195
    public function getSelectedItem() : MenuItemInterface
196
    {
197
        return $this->selectedItemIndex !== null
198
            ? $this->items[$this->selectedItemIndex]
199
            : $this;
200
    }
201
202
    public function getItems() : array
203
    {
204
        return $this->items;
205
    }
206
207
    /**
208
     * Can the item be selected
209
     * In this case, it indicates if at least 1 item inside the SplitItem can be selected
210
     */
211
    public function canSelect() : bool
212
    {
213
        return $this->canBeSelected;
214
    }
215
216
    /**
217
     * Execute the items callable if required
218
     */
219
    public function getSelectAction() : ?callable
220
    {
221
        return null;
222
    }
223
224
    /**
225
     * Whether or not the menu item is showing the menustyle extra value
226
     */
227
    public function showsItemExtra() : bool
228
    {
229
        return false;
230
    }
231
232
    /**
233
     * Enable showing item extra
234
     */
235
    public function showItemExtra() : void
236
    {
237
        //noop
238
    }
239
240
    /**
241
     * Disable showing item extra
242
     */
243
    public function hideItemExtra() : void
244
    {
245
        //noop
246
    }
247
248
    /**
249
     * Nothing to return with SplitItem
250
     */
251
    public function getText() : string
252
    {
253
        throw new \BadMethodCallException(sprintf('Not supported on: %s', __CLASS__));
254
    }
255
}
256