Completed
Push — master ( 306131...5909ec )
by Aydin
06:33 queued 03:57
created

src/Builder/CliMenuBuilder.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace PhpSchool\CliMenu\Builder;
4
5
use PhpSchool\CliMenu\Action\ExitAction;
6
use PhpSchool\CliMenu\Action\GoBackAction;
7
use PhpSchool\CliMenu\MenuItem\AsciiArtItem;
8
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
9
use PhpSchool\CliMenu\MenuItem\SelectableItem;
10
use PhpSchool\CliMenu\MenuItem\SplitItem;
11
use PhpSchool\CliMenu\CliMenu;
12
use PhpSchool\CliMenu\MenuStyle;
13
use PhpSchool\CliMenu\Terminal\TerminalFactory;
14
use PhpSchool\Terminal\Terminal;
15
use RuntimeException;
16
17
/**
18
 * @author Michael Woodward <[email protected]>
19
 * @author Aydin Hassan <[email protected]>
20
 */
21
class CliMenuBuilder implements Builder
22
{
23
    use BuilderUtils;
24
25
    /**
26
     * @var null|Builder
27
     */
28
    private $parent;
29
    
30
    /**
31
     * @var bool
32
     */
33
    private $isBuilt = false;
34
35
    /**
36
     * @var SplitItemBuilder[]
37
     */
38
    private $splitItemBuilders = [];
39
40
    /**
41
     * @var SplitItem[]
42
     */
43
    private $splitItems = [];
44
45
    /**
46
     * @var string
47
     */
48
    private $goBackButtonText = 'Go Back';
49
50
    /**
51
     * @var string
52
     */
53
    private $exitButtonText = 'Exit';
54
55
    /**
56
     * @var MenuStyle
57
     */
58
    private $style;
59
60
    /**
61
     * @var Terminal
62
     */
63
    private $terminal;
64
65
    /**
66
     * @var string
67
     */
68
    private $menuTitle;
69
70
    /**
71
     * @var bool
72
     */
73
    private $disableDefaultItems = false;
74
75
    /**
76
     * @var bool
77
     */
78
    private $disabled = false;
79
80
    public function __construct(Terminal $terminal = null, Builder $parent = null)
81
    {
82
        $this->terminal = $terminal ?? TerminalFactory::fromSystem();
83
        $this->parent   = $parent;
84
        $this->style = new MenuStyle($this->terminal);
85
    }
86
    
87
    public static function newFromParent(Builder $parent) : self
88
    {
89
        return new self($parent->getTerminal(), $parent);
90
    }
91
92
    public function setTitle(string $title) : self
93
    {
94
        $this->menuTitle = $title;
95
96
        return $this;
97
    }
98
99
    public function addMenuItem(MenuItemInterface $item) : self
100
    {
101
        $this->menuItems[] = $item;
102
103
        return $this;
104
    }
105
106
    public function addItems(array $items) : self
107
    {
108
        foreach ($items as $item) {
109
            $this->addItem(...$item);
0 ignored issues
show
The call to addItem() misses a required argument $itemCallable.

This check looks for function calls that miss required arguments.

Loading history...
110
        }
111
112
        return $this;
113
    }
114
115
    public function addAsciiArt(string $art, string $position = AsciiArtItem::POSITION_CENTER, string $alt = '') : self
116
    {
117
        $this->addMenuItem(new AsciiArtItem($art, $position, $alt));
118
119
        return $this;
120
    }
121
122
    /**
123
     * Add a split item
124
     */
125
    public function addSplitItem() : SplitItemBuilder
126
    {
127
        $this->menuItems[] = [
128
            'type' => 'splititem-placeholder',
129
            'id'   => $id = uniqid('', true),
130
        ];
131
                
132
        $this->splitItemBuilders[$id] = new SplitItemBuilder($this);
133
        return $this->splitItemBuilders[$id];
134
    }
135
136
    /**
137
     * Disable a submenu
138
     *
139
     * @throws \InvalidArgumentException
140
     */
141
    public function disableMenu() : self
142
    {
143
        if (!$this->parent) {
144
            throw new \InvalidArgumentException(
145
                'You can\'t disable the root menu'
146
            );
147
        }
148
149
        $this->disabled = true;
150
151
        return $this;
152
    }
153
154
    public function isMenuDisabled() : bool
155
    {
156
        return $this->disabled;
157
    }
158
159
    public function setGoBackButtonText(string $goBackButtonTest) : self
160
    {
161
        $this->goBackButtonText = $goBackButtonTest;
162
163
        return $this;
164
    }
165
166
    public function setExitButtonText(string $exitButtonText) : self
167
    {
168
        $this->exitButtonText = $exitButtonText;
169
170
        return $this;
171
    }
172
173
    public function setBackgroundColour(string $colour, string $fallback = null) : self
174
    {
175
        $this->style->setBg($colour, $fallback);
176
177
        return $this;
178
    }
179
180
    public function setForegroundColour(string $colour, string $fallback = null) : self
181
    {
182
        $this->style->setFg($colour, $fallback);
183
184
        return $this;
185
    }
186
187
    public function setWidth(int $width) : self
188
    {
189
        $this->style->setWidth($width);
190
191
        return $this;
192
    }
193
194
    public function setPadding(int $topBottom, int $leftRight = null) : self
195
    {
196
        $this->style->setPadding($topBottom, $leftRight);
197
198
        return $this;
199
    }
200
201
    public function setPaddingTopBottom(int $topBottom) : self
202
    {
203
        $this->style->setPaddingTopBottom($topBottom);
204
205
        return $this;
206
    }
207
208
    public function setPaddingLeftRight(int $leftRight) : self
209
    {
210
        $this->style->setPaddingLeftRight($leftRight);
211
212
        return $this;
213
    }
214
215
    public function setMarginAuto() : self
216
    {
217
        $this->style->setMarginAuto();
218
219
        return $this;
220
    }
221
222
    public function setMargin(int $margin) : self
223
    {
224
        $this->style->setMargin($margin);
225
226
        return $this;
227
    }
228
229
    public function setUnselectedMarker(string $marker) : self
230
    {
231
        $this->style->setUnselectedMarker($marker);
232
233
        return $this;
234
    }
235
236
    public function setSelectedMarker(string $marker) : self
237
    {
238
        $this->style->setSelectedMarker($marker);
239
240
        return $this;
241
    }
242
243
    public function setItemExtra(string $extra) : self
244
    {
245
        $this->style->setItemExtra($extra);
246
247
        return $this;
248
    }
249
250
    public function setTitleSeparator(string $separator) : self
251
    {
252
        $this->style->setTitleSeparator($separator);
253
254
        return $this;
255
    }
256
257
    public function setBorder(int $top, $right = null, $bottom = null, $left = null, string $colour = null) : self
258
    {
259
        $this->style->setBorder($top, $right, $bottom, $left, $colour);
260
261
        return $this;
262
    }
263
264
    public function setBorderTopWidth(int $width) : self
265
    {
266
        $this->style->setBorderTopWidth($width);
267
        
268
        return $this;
269
    }
270
271
    public function setBorderRightWidth(int $width) : self
272
    {
273
        $this->style->setBorderRightWidth($width);
274
275
        return $this;
276
    }
277
278
    public function setBorderBottomWidth(int $width) : self
279
    {
280
        $this->style->setBorderBottomWidth($width);
281
282
        return $this;
283
    }
284
285
    public function setBorderLeftWidth(int $width) : self
286
    {
287
        $this->style->setBorderLeftWidth($width);
288
289
        return $this;
290
    }
291
292
    public function setBorderColour(string $colour, $fallback = null) : self
293
    {
294
        $this->style->setBorderColour($colour, $fallback);
295
296
        return $this;
297
    }
298
299
    public function getTerminal() : Terminal
300
    {
301
        return $this->terminal;
302
    }
303
304
    private function getDefaultItems() : array
305
    {
306
        $actions = [];
307
        if ($this->parent) {
308
            $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction);
309
        }
310
311
        $actions[] = new SelectableItem($this->exitButtonText, new ExitAction);
312
        return $actions;
313
    }
314
315
    public function disableDefaultItems() : self
316
    {
317
        $this->disableDefaultItems = true;
318
319
        return $this;
320
    }
321
322
    private function itemsHaveExtra(array $items) : bool
323
    {
324
        return !empty(array_filter($items, function (MenuItemInterface $item) {
325
            return $item->showsItemExtra();
326
        }));
327
    }
328
329
    /**
330
     * Recursively drop back to the parents menu style
331
     * when the current menu has a parent and has no changes
332
     */
333
    public function getMenuStyle() : MenuStyle
334
    {
335
        if (null === $this->parent) {
336
            return $this->style;
337
        }
338
339
        if ($this->style->hasChangedFromDefaults()) {
340
            return $this->style;
341
        }
342
343
        return $this->parent->getMenuStyle();
344
    }
345
346
    /**
347
     * @throws RuntimeException
348
     */
349
    public function getSubMenu(string $id) : CliMenu
350
    {
351
        if (false === $this->isBuilt) {
352
            throw new RuntimeException(sprintf('Menu: "%s" cannot be retrieved until menu has been built', $id));
353
        }
354
355
        return $this->subMenus[$id];
356
    }
357
    
358
    private function buildSplitItems(array $items) : array
359
    {
360
        return array_map(function ($item) {
361
            if (!is_array($item) || $item['type'] !== 'splititem-placeholder') {
362
                return $item;
363
            }
364
365
            $splitItemBuilder        = $this->splitItemBuilders[$item['id']];
366
            $this->splitItems[$item['id']] = $splitItemBuilder->build();
367
368
            return $this->splitItems[$item['id']];
369
        }, $items);
370
    }
371
372
    public function build() : CliMenu
373
    {
374
        $this->isBuilt = true;
375
376
        $mergedItems = $this->disableDefaultItems
377
            ? $this->menuItems
378
            : array_merge($this->menuItems, $this->getDefaultItems());
379
380
        
381
        $menuItems = $this->buildSplitItems($mergedItems);
382
        $menuItems = $this->buildSubMenus($menuItems);
383
384
        $this->style->setDisplaysExtra($this->itemsHaveExtra($menuItems));
385
386
        $menu = new CliMenu(
387
            $this->menuTitle,
388
            $menuItems,
389
            $this->terminal,
390
            $this->getMenuStyle()
391
        );
392
393
        foreach ($this->subMenus as $subMenu) {
394
            $subMenu->setParent($menu);
395
        }
396
        
397
        foreach ($this->splitItemBuilders as $splitItemBuilder) {
398
            $splitItemBuilder->setSubMenuParents($menu);
399
        }
400
401
        return $menu;
402
    }
403
404
    /**
405
     * Return to parent builder
406
     *
407
     * @return CliMenuBuilder|SplitItemBuilder
408
     */
409
    public function end() : ?Builder
410
    {
411
        if (null === $this->parent) {
412
            throw new RuntimeException('No parent builder to return to');
413
        }
414
415
        return $this->parent;
416
    }
417
}
418