Completed
Push — master ( c7bdb1...5472d7 )
by Aydin
04:34
created

CliMenuBuilder::setSelectedMarker()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
1
<?php
2
3
namespace PhpSchool\CliMenu;
4
5
use PhpSchool\CliMenu\Action\ExitAction;
6
use PhpSchool\CliMenu\Action\GoBackAction;
7
use PhpSchool\CliMenu\MenuItem\AsciiArtItem;
8
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
9
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
10
use PhpSchool\CliMenu\MenuItem\MenuMenuItem;
11
use PhpSchool\CliMenu\MenuItem\SelectableItem;
12
use PhpSchool\CliMenu\MenuItem\StaticItem;
13
use PhpSchool\CliMenu\Terminal\TerminalFactory;
14
use PhpSchool\CliMenu\Terminal\TerminalInterface;
15
use Assert\Assertion;
16
use RuntimeException;
17
18
/**
19
 * Class CliMenuBuilder
20
 *
21
 * @package PhpSchool\CliMenu
22
 * @author Michael Woodward <[email protected]>
23
 * @author Aydin Hassan <[email protected]>
24
 */
25
class CliMenuBuilder
26
{
27
    /**
28
     * @var bool
29
     */
30
    private $isBuilt = false;
31
32
    /**
33
     * @var null|self
34
     */
35
    private $parent;
36
    
37
    /**
38
     * @var self[]|CliMenu[]
39
     */
40
    private $subMenus = [];
41
42
    /**
43
     * @var string
44
     */
45
    private $goBackButtonText = 'Go Back';
46
    
47
    /**
48
     * @var string
49
     */
50
    private $exitButtonText = 'Exit';
51
52
    /**
53
     * @var array
54
     */
55
    private $menuItems = [];
56
57
    /**
58
     * @var array
59
     */
60
    private $style;
61
62
    /**
63
     * @var TerminalInterface
64
     */
65
    private $terminal;
66
67
    /**
68
     * @var string
69
     */
70
    private $menuTitle;
71
72
    /**
73
     * @var bool
74
     */
75
    private $disableDefaultItems = false;
76
77
    /**
78
     * @var bool
79
     */
80
    private $disabled = false;
81
82
    /**
83
     * @param CliMenuBuilder|null $parent
84
     */
85
    public function __construct(CliMenuBuilder $parent = null)
86
    {
87
        $this->parent   = $parent;
88
        $this->terminal = TerminalFactory::fromSystem();
89
        $this->style    = MenuStyle::getDefaultStyleValues();
90
    }
91
92
    /**
93
     * @param string $title
94
     * @return $this
95
     */
96
    public function setTitle($title)
97
    {
98
        Assertion::string($title);
99
100
        $this->menuTitle = $title;
101
102
        return $this;
103
    }
104
105
    /**
106
     * @param MenuItemInterface $item
107
     * @return $this
108
     */
109
    public function addMenuItem(MenuItemInterface $item)
110
    {
111
        $this->menuItems[] = $item;
112
113
        return $this;
114
    }
115
116
    /**
117
     * @param string $text
118
     * @param callable $itemCallable
119
     * @param bool $showItemExtra
120
     * @param bool $disabled
121
     * @return $this
122
     */
123
    public function addItem($text, callable $itemCallable, $showItemExtra = false, $disabled = false)
124
    {
125
        Assertion::string($text);
126
127
        $this->addMenuItem(new SelectableItem($text, $itemCallable, $showItemExtra, $disabled));
128
129
        return $this;
130
    }
131
132
    /**
133
     * @param array $items
134
     * @return $this
135
     */
136
    public function addItems(array $items)
137
    {
138
        foreach ($items as $item) {
139
            $this->addItem(...$item);
0 ignored issues
show
Bug introduced by
The call to addItem() misses a required argument $itemCallable.

This check looks for function calls that miss required arguments.

Loading history...
140
        }
141
        
142
        return $this;
143
    }
144
145
    /**
146
     * @param string $text
147
     * @return $this
148
     */
149
    public function addStaticItem($text)
150
    {
151
        Assertion::string($text);
152
153
        $this->addMenuItem(new StaticItem($text));
154
155
        return $this;
156
    }
157
158
    /**
159
     * @param string $breakChar
160
     * @param int $lines
161
     * @return $this
162
     */
163
    public function addLineBreak($breakChar = ' ', $lines = 1)
164
    {
165
        Assertion::string($breakChar);
166
        Assertion::integer($lines);
167
168
        $this->addMenuItem(new LineBreakItem($breakChar, $lines));
169
170
        return $this;
171
    }
172
173
    /**
174
     * @param string $art
175
     * @param string $position
176
     * @return $this
177
     */
178
    public function addAsciiArt($art, $position = AsciiArtItem::POSITION_CENTER)
179
    {
180
        Assertion::string($art);
181
        Assertion::string($position);
182
183
        $this->addMenuItem(new AsciiArtItem($art, $position));
184
185
        return $this;
186
    }
187
188
    /**
189
     * @param string $id ID to reference and retrieve sub-menu
190
     * @return CliMenuBuilder
191
     */
192
    public function addSubMenu($id)
193
    {
194
        Assertion::string($id);
195
196
        $this->menuItems[]   = $id;
197
        $this->subMenus[$id] = new self($this);
198
199
        return $this->subMenus[$id];
200
    }
201
202
    /**
203
     * Disable a submenu
204
     * @throws \InvalidArgumentException
205
     * @return $this
206
     */
207
    public function disableMenu()
208
    {
209
        if (!$this->parent) {
210
            throw new \InvalidArgumentException(
211
                'You can\'t disable the root menu'
212
            );
213
        }
214
215
        $this->disabled = true;
216
217
        return $this;
218
    }
219
220
    /**
221
     * @return bool
222
     */
223
    public function isMenuDisabled()
224
    {
225
        return $this->disabled;
226
    }
227
228
    /**
229
     * @param string $goBackButtonTest
230
     * @return $this
231
     */
232
    public function setGoBackButtonText($goBackButtonTest)
233
    {
234
        $this->goBackButtonText = $goBackButtonTest;
235
        
236
        return $this;
237
    }
238
239
    /**
240
     * @param string $exitButtonText
241
     * @return $this
242
     */
243
    public function setExitButtonText($exitButtonText)
244
    {
245
        $this->exitButtonText = $exitButtonText;
246
        
247
        return $this;
248
    }
249
250
    /**
251
     * @param string $colour
252
     * @return $this
253
     */
254
    public function setBackgroundColour($colour)
255
    {
256
        Assertion::inArray($colour, MenuStyle::getAvailableColours());
257
258
        $this->style['bg'] = $colour;
259
260
        return $this;
261
    }
262
263
    /**
264
     * @param string $colour
265
     * @return $this
266
     */
267
    public function setForegroundColour($colour)
268
    {
269
        Assertion::inArray($colour, MenuStyle::getAvailableColours());
270
271
        $this->style['fg'] = $colour;
272
273
        return $this;
274
    }
275
276
    /**
277
     * @param int $width
278
     * @return $this
279
     */
280
    public function setWidth($width)
281
    {
282
        Assertion::integer($width);
283
284
        $this->style['width'] = $width;
285
286
        return $this;
287
    }
288
289
    /**
290
     * @param int $padding
291
     * @return $this
292
     */
293
    public function setPadding($padding)
294
    {
295
        Assertion::integer($padding);
296
297
        $this->style['padding'] = $padding;
298
299
        return $this;
300
    }
301
302
    /**
303
     * @param int $margin
304
     * @return $this
305
     */
306
    public function setMargin($margin)
307
    {
308
        Assertion::integer($margin);
309
310
        $this->style['margin'] = $margin;
311
312
        return $this;
313
    }
314
315
    /**
316
     * @param string $marker
317
     * @return $this
318
     */
319
    public function setUnselectedMarker($marker)
320
    {
321
        Assertion::string($marker);
322
323
        $this->style['unselectedMarker'] = $marker;
324
325
        return $this;
326
    }
327
328
    /**
329
     * @param string $marker
330
     * @return $this
331
     */
332
    public function setSelectedMarker($marker)
333
    {
334
        Assertion::string($marker);
335
336
        $this->style['selectedMarker'] = $marker;
337
338
        return $this;
339
    }
340
341
    /**
342
     * @param string $extra
343
     * @return $this
344
     */
345
    public function setItemExtra($extra)
346
    {
347
        Assertion::string($extra);
348
349
        $this->style['itemExtra'] = $extra;
350
351
        return $this;
352
    }
353
354
    /**
355
     * @param string $separator
356
     * @return $this
357
     */
358
    public function setTitleSeparator($separator)
359
    {
360
        Assertion::string($separator);
361
362
        $this->style['titleSeparator'] = $separator;
363
364
        return $this;
365
    }
366
367
    /**
368
     * @param TerminalInterface $terminal
369
     * @return $this
370
     */
371
    public function setTerminal(TerminalInterface $terminal)
372
    {
373
        $this->terminal = $terminal;
374
        return $this;
375
    }
376
377
    /**
378
     * @return array
379
     */
380
    private function getDefaultItems()
381
    {
382
        $actions = [];
383
        if ($this->parent) {
384
            $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction);
385
        }
386
        
387
        $actions[] = new SelectableItem($this->exitButtonText, new ExitAction);
388
        return $actions;
389
    }
390
391
    /**
392
     * @return $this
393
     */
394
    public function disableDefaultItems()
395
    {
396
        $this->disableDefaultItems = true;
397
398
        return $this;
399
    }
400
401
    /**
402
     * @param array $items
403
     * @return bool
404
     */
405
    private function itemsHaveExtra(array $items)
406
    {
407
        return !empty(array_filter($items, function (MenuItemInterface $item) {
408
            return $item->showsItemExtra();
409
        }));
410
    }
411
412
    /**
413
     * Recursively drop back to the parents menu style
414
     * when the current menu has a parent and has no changes
415
     *
416
     * @return MenuStyle
417
     */
418
    private function getMenuStyle()
419
    {
420
        if (null === $this->parent) {
421
            return $this->buildStyle();
422
        }
423
424
        if ($this->style !== MenuStyle::getDefaultStyleValues()) {
425
            return $this->buildStyle();
426
        }
427
428
        return $this->parent->getMenuStyle();
429
    }
430
431
    /**
432
     * @return MenuStyle
433
     */
434
    private function buildStyle()
435
    {
436
        return (new MenuStyle($this->terminal))
437
            ->setFg($this->style['fg'])
438
            ->setBg($this->style['bg'])
439
            ->setWidth($this->style['width'])
440
            ->setPadding($this->style['padding'])
441
            ->setMargin($this->style['margin'])
442
            ->setSelectedMarker($this->style['selectedMarker'])
443
            ->setUnselectedMarker($this->style['unselectedMarker'])
444
            ->setItemExtra($this->style['itemExtra'])
445
            ->setDisplaysExtra($this->style['displaysExtra'])
446
            ->setTitleSeparator($this->style['titleSeparator']);
447
    }
448
449
    /**
450
     * Return to parent builder
451
     *
452
     * @return CliMenuBuilder
453
     * @throws RuntimeException
454
     */
455
    public function end()
456
    {
457
        if (null === $this->parent) {
458
            throw new RuntimeException('No parent builder to return to');
459
        }
460
461
        return $this->parent;
462
    }
463
464
    /**
465
     * @param string $id
466
     * @return CliMenu
467
     * @throws RuntimeException
468
     */
469
    public function getSubMenu($id)
470
    {
471
        if (false === $this->isBuilt) {
472
            throw new RuntimeException(sprintf('Menu: "%s" cannot be retrieved until menu has been built', $id));
473
        }
474
475
        return $this->subMenus[$id];
476
    }
477
478
    /**
479
     * @param array $items
480
     * @return array
481
     */
482
    private function buildSubMenus(array $items)
483
    {
484
        return array_map(function ($item) {
485
            if (!is_string($item)) {
486
                return $item;
487
            }
488
489
            $menuBuilder           = $this->subMenus[$item];
490
            $this->subMenus[$item] = $menuBuilder->build();
491
492
            return new MenuMenuItem($item, $this->subMenus[$item], $menuBuilder->isMenuDisabled());
0 ignored issues
show
Bug introduced by
It seems like $this->subMenus[$item] can also be of type object<PhpSchool\CliMenu\CliMenuBuilder>; however, PhpSchool\CliMenu\MenuIt...MenuItem::__construct() does only seem to accept object<PhpSchool\CliMenu\CliMenu>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
493
        }, $items);
494
    }
495
496
    /**
497
     * @return CliMenu
498
     */
499
    public function build()
500
    {
501
        $this->isBuilt = true;
502
503
        $mergedItems = $this->disableDefaultItems
504
            ? $this->menuItems
505
            : array_merge($this->menuItems, $this->getDefaultItems());
506
507
        $menuItems = $this->buildSubMenus($mergedItems);
508
509
        $this->style['displaysExtra'] = $this->itemsHaveExtra($menuItems);
510
511
        $menu = new CliMenu(
512
            $this->menuTitle ?: false,
0 ignored issues
show
Security Bug introduced by
It seems like $this->menuTitle ?: false can also be of type false; however, PhpSchool\CliMenu\CliMenu::__construct() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
513
            $menuItems,
514
            $this->terminal,
515
            $this->getMenuStyle()
516
        );
517
        
518
        foreach ($this->subMenus as $subMenu) {
519
            $subMenu->setParent($menu);
0 ignored issues
show
Bug introduced by
The method setParent() does not seem to exist on object<PhpSchool\CliMenu\CliMenuBuilder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
520
        }
521
522
        return $menu;
523
    }
524
}
525