Skip to content

Commit 116f463

Browse files
authored
Merge pull request #6302 from rivalarya/add-method-promptByMultipleKey-in-CLI
Add new method `promptByMultipleKeys()` in CLI class
2 parents ac4a8f2 + 6f88d82 commit 116f463

File tree

5 files changed

+154
-5
lines changed

5 files changed

+154
-5
lines changed

system/CLI/CLI.php

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,25 +281,119 @@ public static function promptByKey($text, array $options, $validation = null): s
281281
throw new InvalidArgumentException('$text can only be of type string|array');
282282
}
283283

284-
if (! $options) {
285-
throw new InvalidArgumentException('No options to select from were provided');
286-
}
284+
CLI::isZeroOptions($options);
287285

288286
if ($line = array_shift($text)) {
289287
CLI::write($line);
290288
}
291289

290+
CLI::printKeysAndValues($options);
291+
292+
return static::prompt(PHP_EOL . array_shift($text), array_keys($options), $validation);
293+
}
294+
295+
/**
296+
* This method is the same as promptByKey(), but this method supports multiple keys, separated by commas.
297+
*
298+
* @param string $text Output "field" text or an one or two value array where the first value is the text before listing the options
299+
* and the second value the text before asking to select one option. Provide empty string to omit
300+
* @param array $options A list of options (array(key => description)), the first option will be the default value
301+
*
302+
* @return array The selected key(s) and value(s) of $options
303+
*/
304+
public static function promptByMultipleKeys(string $text, array $options): array
305+
{
306+
CLI::isZeroOptions($options);
307+
308+
$extraOutputDefault = static::color('0', 'green');
309+
$opts = $options;
310+
unset($opts[0]);
311+
312+
if (empty($opts)) {
313+
$extraOutput = $extraOutputDefault;
314+
} else {
315+
$optsKey = [];
316+
317+
foreach (array_keys($opts) as $key) {
318+
$optsKey[] = $key;
319+
}
320+
$extraOutput = '[' . $extraOutputDefault . ', ' . implode(', ', $optsKey) . ']';
321+
$extraOutput = 'You can specify multiple values separated by commas.' . PHP_EOL . $extraOutput;
322+
}
323+
324+
CLI::write($text);
325+
CLI::printKeysAndValues($options);
326+
CLI::newLine();
327+
$input = static::prompt($extraOutput) ?: 0; // 0 is default
328+
329+
// validation
330+
while (true) {
331+
$pattern = preg_match_all('/^\d+(,\d+)*$/', trim($input));
332+
333+
// separate input by comma and convert all to an int[]
334+
$inputToArray = array_map(static fn ($value) => (int) $value, explode(',', $input));
335+
// find max from key of $options
336+
$maxOptions = array_key_last($options);
337+
// find max from input
338+
$maxInput = max($inputToArray);
339+
340+
// return the prompt again if $input contain(s) non-numeric charachter, except a comma.
341+
// And if max from $options less than max from input
342+
// it is mean user tried to access null value in $options
343+
if (! $pattern || $maxOptions < $maxInput) {
344+
static::error('Please select correctly.');
345+
CLI::newLine();
346+
$input = static::prompt($extraOutput) ?: 0;
347+
} else {
348+
break;
349+
}
350+
}
351+
352+
$input = [];
353+
354+
foreach ($options as $key => $description) {
355+
foreach ($inputToArray as $inputKey) {
356+
if ($key === $inputKey) {
357+
$input[$key] = $description;
358+
}
359+
}
360+
}
361+
362+
return $input;
363+
}
364+
365+
//--------------------------------------------------------------------
366+
// Utility for promptBy...
367+
//--------------------------------------------------------------------
368+
369+
/**
370+
* Validation for $options in promptByKey() and promptByMultipleKeys(). Return an error if $options is an empty array.
371+
*/
372+
private static function isZeroOptions(array $options): void
373+
{
374+
if (! $options) {
375+
throw new InvalidArgumentException('No options to select from were provided');
376+
}
377+
}
378+
379+
/**
380+
* Print each key and value one by one
381+
*/
382+
private static function printKeysAndValues(array $options): void
383+
{
292384
// +2 for the square brackets around the key
293385
$keyMaxLength = max(array_map('mb_strwidth', array_keys($options))) + 2;
294386

295387
foreach ($options as $key => $description) {
296388
$name = str_pad(' [' . $key . '] ', $keyMaxLength + 4, ' ');
297389
CLI::write(CLI::color($name, 'green') . CLI::wrap($description, 125, $keyMaxLength + 4));
298390
}
299-
300-
return static::prompt(PHP_EOL . array_shift($text), array_keys($options), $validation);
301391
}
302392

393+
//--------------------------------------------------------------------
394+
// End Utility for promptBy...
395+
//--------------------------------------------------------------------
396+
303397
/**
304398
* Validate one prompt "field" at a time
305399
*

tests/system/CLI/CLITest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,26 @@ public function testPrompt()
9090
PhpStreamWrapper::restore();
9191
}
9292

93+
public function testPromptByMultipleKeys()
94+
{
95+
PhpStreamWrapper::register();
96+
97+
$input = '0,1';
98+
PhpStreamWrapper::setContent($input);
99+
100+
$options = ['Playing game', 'Sleep', 'Badminton'];
101+
$output = CLI::promptByMultipleKeys('Select your hobbies:', $options);
102+
103+
$expected = [
104+
0 => 'Playing game',
105+
1 => 'Sleep',
106+
];
107+
108+
$this->assertSame($expected, $output);
109+
110+
PhpStreamWrapper::restore();
111+
}
112+
93113
public function testIsWindows()
94114
{
95115
$this->assertSame(('\\' === DIRECTORY_SEPARATOR), CLI::isWindows());

user_guide_src/source/changelogs/v4.3.0.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ Others
4747
Enhancements
4848
************
4949

50+
CLI
51+
===
52+
- Added methods ``CLI::promptByMultipleKeys()`` to support multiple value in input, unlike ``promptByKey()``. See :ref:`prompt-by-multiple-keys` for details.
53+
54+
Others
55+
======
5056
- Added the ``StreamFilterTrait`` to make it easier to work with capturing data from STDOUT and STDERR streams. See :ref:`testing-cli-output`.
5157
- Added the ``PhpStreamWrapper`` to make it easier to work with setting data to ``php://stdin``. See :ref:`testing-cli-input`.
5258
- Added before and after events to ``BaseModel::insertBatch()`` and ``BaseModel::updateBatch()`` methods. See :ref:`model-events-callbacks`.

user_guide_src/source/cli/cli_library.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ Named keys are also possible:
7272

7373
Finally, you can pass :ref:`validation <validation>` rules to the answer input as the third parameter, the acceptable answers are automatically restricted to the passed options.
7474

75+
.. _prompt-by-multiple-keys:
76+
77+
promptByMultipleKeys()
78+
======================
79+
80+
This method is the same as ``promptByKey()``, but it supports multiple value.
81+
82+
.. literalinclude:: cli_library/023.php
83+
84+
.. important:: The method ``promptByMultipleKeys()``, unlike ``promptByKey()``, does not support named keys or validation.
85+
7586
Providing Feedback
7687
******************
7788

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
$hobbies = CLI::promptByMultipleKeys('Select your hobbies:', ['Playing game', 'Sleep', 'Badminton']);
4+
/*
5+
* Select your hobbies:
6+
* [0] Playing game
7+
* [1] Sleep
8+
* [2] Badminton
9+
*
10+
* You can specify multiple values separated by commas.
11+
* [0, 1, 2]:
12+
*
13+
* if your answer is '0,2', the return is the key and the value of the options :
14+
* [
15+
* [0] => "Playing game",
16+
* [2] => "Badminton"
17+
* ]
18+
*/

0 commit comments

Comments
 (0)