Prompting for missing input in Artisan commands
Published 02/02/2023 | Last updated 03/02/2023 | 3431 viewsIn the past, I've talked about having fallback prompts for arguments in CLI apps. Now, Laravel can do the heavy lifting for you! Let's take a look.
In one of my talks from Laracon Online, I provided a hot tip I like to implement when it comes to Artisan commands: setting required input to optional, and instead asking the user for it using a question. It looked something like this:
class RegisterUser extends Command{ protected $signature = 'register {email?} {name?} {password?}'; protected $description = 'Register a new user.'; public function handle() { $user = User::create([ 'email' => $this->argument('email') ?? $this->ask("What is the user's email?"), 'name' => $this->argument('name') ?? $this->ask("What is the user's name?"), 'password' => $this->argument('password') ?? $this->ask("What password should be used?"), ]); $this->line("User registered successfully!"); }}
With this in place, a user could pass some, all or none of the required arguments, and we'd just ask for anything they've missed.
php artisan register taylor@mail.com > "What is the user's name?""Taylor Otwell" > "What password should be used?"p4ssw0rd > "User registered successfully!"
Pretty nice, hey? There are, as always, a few caveats with this approach. First of all, we're having to make our CLI arguments optional, and we're relying on the fact that we've not forgotten to ask for any missing values in our code. Is there a better way?
The better way (as of Laravel 9.49)
In Laravel 9.49, Jess Archer contributed an awesome PR to help you easily drop this functionality into your Artisan commands, using nothing more than a simple interface.
This functionality has already been added to all the
php artisan make:*
commands that Laravel ships with, so you'll benefit from this even if you're not one for making your own Artisan commands.
Let's implement this new magic in the command we created earlier:
+use Illuminate\Contracts\Console\PromptsForMissingInput; -class RegisterUser extends Command +class RegisterUser extends Command implements PromptsForMissingInput {- protected $signature = 'register {email?} {name?} {password?}'; + protected $signature = 'register {email} {name} {password}'; protected $description = 'Register a new user.'; public function handle() { $user = User::create([- 'email' => $this->argument('email') ?? $this->ask("What is the user's email?"), - 'name' => $this->argument('name') ?? $this->ask("What is the user's name?"), - 'password' => $this->argument('password') ?? $this->ask("What password should be used?"), + 'email' => $this->argument('email'), + 'name' => $this->argument('name'), + 'password' => $this->argument('password'), ]); $this->line("User registered successfully!"); } }
Note that we can now return to requiring our arguments. The magic of requesting the missing input will happen before Laravel validates the arguments. How nice is that?
So what happens if we run this command?
php artisan register taylor@mail.com > "What is name?""Taylor Otwell" > "What is password?"p4ssw0rd > "User registered successfully!"
Not bad for zero configuration! Still, the questions are a bit off. How can we make them sound more natural? Well, if you provide descriptions for your arguments, Laravel will use those instead. Let's update our command.
use Illuminate\Contracts\Console\PromptsForMissingInput; class RegisterUser extends Command implements PromptsForMissingInput{ protected $signature = "register {email : The user's email} {name : The user's name} {password : The password to be used}"; protected $description = 'Register a new user.'; public function handle() { $user = User::create([ 'email' => $this->argument('email'), 'name' => $this->argument('name'), 'password' => $this->argument('password'), ]); $this->line("User registered successfully!"); }}
With descriptions added, the questions Laravel generates become far more readable!
php artisan register taylor@mail.com > "What is the user's name?""Taylor Otwell" > "What is the password to be used?"p4ssw0rd > "User registered successfully!"
Wrapping up
This is what I love about Laravel. It's not much to ask that you manually check for these parameters and manage your own questions, is it? It is for Laravel. That's why we end up with these amazing tools that make working with the framework a delight and result in beautiful, clean and maintainable code.
This PR offers even more than we've just discussed, such as presenting completely custom questions for more complex scenarios and hooking in after the prompts are complete to ask follow up questions as required. Be sure to check out the well documented PR for more detail.
Thanks for reading! Now go write some code!
Regards,
Luke