Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
22 / 22 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
ValidatingCommand | |
100.00% |
22 / 22 |
|
100.00% |
4 / 4 |
10 | |
100.00% |
1 / 1 |
askWithValidation | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
reloadableChoice | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
translate | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
normalizeInput | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Hyde\Publications\Commands; |
6 | |
7 | use Hyde\Console\Concerns\Command; |
8 | use Illuminate\Support\Facades\Validator; |
9 | use RuntimeException; |
10 | |
11 | use function __; |
12 | use function array_merge; |
13 | use function implode; |
14 | use function in_array; |
15 | use function sprintf; |
16 | use function trim; |
17 | use function ucfirst; |
18 | |
19 | /** |
20 | * @internal An extended Command class that provides validation methods. |
21 | * |
22 | * @see \Hyde\Framework\Testing\Feature\ValidatingCommandTest |
23 | */ |
24 | class ValidatingCommand extends Command |
25 | { |
26 | /** @var int How many times can the validation loop run? It is high enough to not affect normal usage. */ |
27 | protected final const MAX_RETRIES = 30; |
28 | |
29 | /** |
30 | * Ask for a CLI input value until we pass validation rules. |
31 | * |
32 | * @param int $retryCount How many times has the question been asked? |
33 | * |
34 | * @throws RuntimeException If the validation fails after MAX_RETRIES attempts. |
35 | */ |
36 | public function askWithValidation( |
37 | string $name, |
38 | string $question, |
39 | array $rules = [], |
40 | mixed $default = null, |
41 | int $retryCount = 0 |
42 | ): string { |
43 | if ($retryCount >= self::MAX_RETRIES) { |
44 | // Prevent infinite loops that may happen due to the method's recursion. |
45 | // For example when running a command in tests or without interaction. |
46 | throw new RuntimeException(sprintf("Too many validation errors trying to validate '$name' with rules: [%s]", implode(', ', $rules))); |
47 | } |
48 | |
49 | $answer = trim((string) $this->ask(ucfirst($question), $default)); |
50 | $validator = Validator::make([$name => self::normalizeInput($answer, $rules)], [$name => $rules]); |
51 | |
52 | if ($validator->passes()) { |
53 | return $answer; |
54 | } |
55 | |
56 | foreach ($validator->errors()->all() as $error) { |
57 | $this->error($this->translate($name, $error)); |
58 | } |
59 | |
60 | return $this->askWithValidation($name, $question, $rules, $default, $retryCount + 1); |
61 | } |
62 | |
63 | /** @param callable<array<string>> $options A function that returns an array of options. It will be re-run if the user hits selects the added 'reload' option. */ |
64 | public function reloadableChoice(callable $options, string $question, string $reloadMessage = 'Reload options', bool $multiple = false): string|array |
65 | { |
66 | $reloadMessage = "<fg=bright-blue>[$reloadMessage]</>"; |
67 | do { |
68 | $selection = $this->choice($question, array_merge([$reloadMessage], $options()), multiple: $multiple); |
69 | } while (in_array($reloadMessage, (array) $selection)); |
70 | |
71 | return $selection; |
72 | } |
73 | |
74 | protected function translate(string $name, string $error): string |
75 | { |
76 | return __($error, [ |
77 | 'attribute' => $name, |
78 | ]); |
79 | } |
80 | |
81 | protected static function normalizeInput(string $value, array $rules): bool|string |
82 | { |
83 | if (in_array('boolean', $rules)) { |
84 | // Since the Laravel validation rule requires booleans to be boolean, but the Symfony |
85 | // console input is a string, so we need to convert it so that it can be validated. |
86 | if ($value === 'true') { |
87 | return true; |
88 | } |
89 | if ($value === 'false') { |
90 | return false; |
91 | } |
92 | } |
93 | |
94 | return $value; |
95 | } |
96 | } |