Skip to content

Commit 4025748

Browse files
Andrea Marco Sartoridriesvintstaylorotwell
authored
[11.x] Define global validation logic for Laravel Prompts (#49497)
* [10.x] Define global validation logic for Laravel Prompts * [10.x] Update validation logic * [10.x] Infer field name through associative array * [10.x] Require latest version of Laravel Prompts * [10.x] Implement fallback validation * [10.x] Extract logic to instantiate the validator * [10.x] Update methods name * formatting * formatting * formatting and literal --------- Co-authored-by: Dries Vints <dries@vints.be> Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 3ae9c40 commit 4025748

File tree

4 files changed

+168
-13
lines changed

4 files changed

+168
-13
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"egulias/email-validator": "^3.2.1|^4.0",
3131
"fruitcake/php-cors": "^1.3",
3232
"guzzlehttp/uri-template": "^1.0",
33-
"laravel/prompts": "^0.1.12",
33+
"laravel/prompts": "^0.1.15",
3434
"laravel/serializable-closure": "^1.3",
3535
"league/commonmark": "^2.2.1",
3636
"league/flysystem": "^3.8.0",

src/Illuminate/Console/Concerns/ConfiguresPrompts.php

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Laravel\Prompts\SelectPrompt;
1313
use Laravel\Prompts\SuggestPrompt;
1414
use Laravel\Prompts\TextPrompt;
15+
use stdClass;
1516
use Symfony\Component\Console\Input\InputInterface;
1617

1718
trait ConfiguresPrompts
@@ -28,6 +29,8 @@ protected function configurePrompts(InputInterface $input)
2829

2930
Prompt::interactive(($input->isInteractive() && defined('STDIN') && stream_isatty(STDIN)) || $this->laravel->runningUnitTests());
3031

32+
Prompt::validateUsing(fn (Prompt $prompt) => $this->validatePrompt($prompt->value(), $prompt->validate));
33+
3134
Prompt::fallbackWhen(windows_os() || $this->laravel->runningUnitTests());
3235

3336
TextPrompt::fallbackUsing(fn (TextPrompt $prompt) => $this->promptUntilValid(
@@ -140,24 +143,93 @@ protected function promptUntilValid($prompt, $required, $validate)
140143
}
141144
}
142145

143-
if ($validate) {
144-
$error = $validate($result);
146+
$error = is_callable($validate) ? $validate($result) : $this->validatePrompt($result, $validate);
145147

146-
if (is_string($error) && strlen($error) > 0) {
147-
$this->components->error($error);
148+
if (is_string($error) && strlen($error) > 0) {
149+
$this->components->error($error);
148150

149-
if ($this->laravel->runningUnitTests()) {
150-
throw new PromptValidationException;
151-
} else {
152-
continue;
153-
}
151+
if ($this->laravel->runningUnitTests()) {
152+
throw new PromptValidationException;
153+
} else {
154+
continue;
154155
}
155156
}
156157

157158
return $result;
158159
}
159160
}
160161

162+
/**
163+
* Validate the given prompt value using the validator.
164+
*
165+
* @param mixed $value
166+
* @param mixed $rules
167+
* @return ?string
168+
*/
169+
protected function validatePrompt($value, $rules)
170+
{
171+
if ($rules instanceof stdClass) {
172+
$messages = $rules->messages ?? [];
173+
$attributes = $rules->attributes ?? [];
174+
$rules = $rules->rules ?? null;
175+
}
176+
177+
178+
if (! $rules) {
179+
return;
180+
}
181+
182+
$field = 'answer';
183+
184+
if (is_array($rules) && ! array_is_list($rules)) {
185+
[$field, $rules] = [key($rules), current($rules)];
186+
}
187+
188+
return $this->getPromptValidatorInstance(
189+
$field, $value, $rules, $messages ?? [], $attributes ?? []
190+
)->errors()->first();
191+
}
192+
193+
/**
194+
* Get the validator instance that should be used to validate prompts.
195+
*
196+
* @param string $value
197+
* @param mixed $value
198+
* @param mixed $rules
199+
* @param array $messages
200+
* @param array $attributes
201+
* @return \Illuminate\Validation\Validator
202+
*/
203+
protected function getPromptValidatorInstance($field, $value, $rules, array $messages = [], array $attributes = [])
204+
{
205+
return $this->laravel['validator']->make(
206+
[$field => $value],
207+
[$field => $rules],
208+
empty($messages) ? $this->validationMessages() : $messages,
209+
empty($attributes) ? $this->validationAttributes() : $attributes,
210+
);
211+
}
212+
213+
/**
214+
* Get the validation messages that should be used during prompt validation.
215+
*
216+
* @return array
217+
*/
218+
protected function validationMessages()
219+
{
220+
return [];
221+
}
222+
223+
/**
224+
* Get the validation attributes that should be used during prompt validation.
225+
*
226+
* @return array
227+
*/
228+
protected function validationAttributes()
229+
{
230+
return [];
231+
}
232+
161233
/**
162234
* Restore the prompts output.
163235
*

src/Illuminate/Support/helpers.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,18 @@ function filled($value)
152152
}
153153
}
154154

155+
if (! function_exists('literal')) {
156+
/**
157+
* Create a new anonymous object using named arguments.
158+
*
159+
* @return \stdClass
160+
*/
161+
function literal(...$arguments)
162+
{
163+
return (object) $arguments;
164+
}
165+
}
166+
155167
if (! function_exists('object_get')) {
156168
/**
157169
* Get an item from an object using "dot" notation.

tests/Integration/Console/PromptsValidationTest.php

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,41 @@ class PromptsValidationTest extends TestCase
1313
protected function defineEnvironment($app)
1414
{
1515
$app[Kernel::class]->registerCommand(new DummyPromptsValidationCommand());
16+
$app[Kernel::class]->registerCommand(new DummyPromptsWithLaravelRulesCommand());
17+
$app[Kernel::class]->registerCommand(new DummyPromptsWithLaravelRulesMessagesAndAttributesCommand());
18+
$app[Kernel::class]->registerCommand(new DummyPromptsWithLaravelRulesCommandWithInlineMessagesAndAttibutesCommand());
1619
}
1720

1821
public function testValidationForPrompts()
1922
{
2023
$this
2124
->artisan(DummyPromptsValidationCommand::class)
22-
->expectsQuestion('Test', 'bar')
23-
->expectsOutputToContain('error!');
25+
->expectsQuestion('What is your name?', '')
26+
->expectsOutputToContain('Required!');
27+
}
28+
29+
public function testValidationWithLaravelRulesAndNoCustomization()
30+
{
31+
$this
32+
->artisan(DummyPromptsWithLaravelRulesCommand::class)
33+
->expectsQuestion('What is your name?', '')
34+
->expectsOutputToContain('The answer field is required.');
35+
}
36+
37+
public function testValidationWithLaravelRulesInlineMessagesAndAttributes()
38+
{
39+
$this
40+
->artisan(DummyPromptsWithLaravelRulesCommandWithInlineMessagesAndAttibutesCommand::class)
41+
->expectsQuestion('What is your name?', '')
42+
->expectsOutputToContain('Your full name is mandatory.');
43+
}
44+
45+
public function testValidationWithLaravelRulesMessagesAndAttributes()
46+
{
47+
$this
48+
->artisan(DummyPromptsWithLaravelRulesMessagesAndAttributesCommand::class)
49+
->expectsQuestion('What is your name?', '')
50+
->expectsOutputToContain('Your full name is mandatory.');
2451
}
2552
}
2653

@@ -30,6 +57,50 @@ class DummyPromptsValidationCommand extends Command
3057

3158
public function handle()
3259
{
33-
text('Test', validate: fn ($value) => $value == 'foo' ? '' : 'error!');
60+
text('What is your name?', validate: fn ($value) => $value == '' ? 'Required!' : null);
61+
}
62+
}
63+
64+
class DummyPromptsWithLaravelRulesCommand extends Command
65+
{
66+
protected $signature = 'prompts-laravel-rules-test';
67+
68+
public function handle()
69+
{
70+
text('What is your name?', validate: 'required');
71+
}
72+
}
73+
74+
class DummyPromptsWithLaravelRulesCommandWithInlineMessagesAndAttibutesCommand extends Command
75+
{
76+
protected $signature = 'prompts-laravel-rules-inline-test';
77+
78+
public function handle()
79+
{
80+
text('What is your name?', validate: literal(
81+
rules: ['name' => 'required'],
82+
messages: ['name.required' => 'Your :attribute is mandatory.'],
83+
attributes: ['name' => 'full name'],
84+
));
85+
}
86+
}
87+
88+
class DummyPromptsWithLaravelRulesMessagesAndAttributesCommand extends Command
89+
{
90+
protected $signature = 'prompts-laravel-rules-messages-attributes-test';
91+
92+
public function handle()
93+
{
94+
text('What is your name?', validate: ['name' => 'required']);
95+
}
96+
97+
protected function validationMessages()
98+
{
99+
return ['name.required' => 'Your :attribute is mandatory.'];
100+
}
101+
102+
protected function validationAttributes()
103+
{
104+
return ['name' => 'full name'];
34105
}
35106
}

0 commit comments

Comments
 (0)