How to make your Laravel Console commands interactive

Tom Ellis
4 min readFeb 2, 2023

--

From working with Symfony Components alot, while tinkering around I found that the Console Component provides functionality for interactive commands, which isn’t immediately obvious for some users of Laravel.

An interactive command means you don’t have to specify arguments/options when running a command, and that you can interactively ask for him if they aren’t passed.

You would usually start by adding this to your command:

    /**
* Interacts with the user.
*
* This method is executed before the InputDefinition is validated.
* This means that this is the only place where the command can
* interactively ask for values of missing required arguments.
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
}

Symfony’s definition of this method:

Its purpose is to check if some of the options/arguments are missing and interactively ask the user for those values. This is the last place where you can ask for missing options/arguments. After this command, missing options/arguments will result in an error.

How can I do this in Laravel commands?

Its quite easy to get this working in a Laravel command.

Using the same command from one of my previous posts, which is a simple command for saying hello to a given name passed when running the command.

<?php

namespace App\Console\Commands;

use SmashedEgg\LaravelConsole\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class SayHelloCommand extends Command
{
protected $signature = 'say:hello {name : Name}';

protected $description = 'Say hello to someone';

public function handle()
{
$this->info('Hello ' . $this->argument('name'));
return 0;
}
}

If you run php artisan say:hello Tom it works as expected.

If you run php artisan say:hello with no arguments, it errors as it normal would.

See below for an example output from running both commands:

First we need to add the interact method with the relevant checks.

protected function interact(InputInterface $input, OutputInterface $output)
{
if ( ! $input->getArgument('name')) {
$input->setArgument('name', $this->ask('Name ?'));
}
}

Which would result in the following structure:

<?php

namespace App\Console\Commands;

use SmashedEgg\LaravelConsole\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class SayHelloCommand extends Command
{
protected $signature = 'say:hello {name : Name}';

protected $description = 'Say hello to someone';

public function handle()
{
$this->info('Hello ' . $this->argument('name'));
return 0;
}

protected function interact(InputInterface $input, OutputInterface $output)
{
if ( ! $input->getArgument('name')) {
$input->setArgument('name', $this->ask('Name ?'));
}
}
}

All this command is doing is going, do we have an argument for name passed, if not, lets ask for it.

Now when we run the command again, we’re prompted to enter a name:

You can also disable interaction by passing -n or --no-interaction to the command, which would be useful where the scripts are ran as part of a cron or deployment process.

Pros

  1. It makes console commands easier to run for different environments. i.e locally I might run it interactively, and in scripts ran in production, I would list out the arguments.

Cons

  1. It doesn’t look very Laravel friendly.

Alternative Solution 2

I created a package to make this setup alot nicer. Which you can find here. It also supports command aliases, which I talk about here.

It supports Laravel 9 currently. Support for the upcoming Laravel 10 is due in the near future.

First we need to install the package:

composer require smashed-egg/laravel-console

Next we need to update the command, so it now extends:

SmashedEgg\LaravelConsole\Command

instead of

Illuminate\Console\Command

We extend this in our class, giving you the same Laravel functionality.

Next we can remove this:


protected function interact(InputInterface $input, OutputInterface $output)
{
if ( ! $input->getArgument('name')) {
$input->setArgument('name', $this->ask('Name ?'));
}
}

And replace it with this:

public function interactInteract()
{
if ( ! $this->input->getArgument('name')) {
$this->input->setArgument('name', $this->ask('Name ?'));
}
}

Resulting in the command which looks like this:

<?php

namespace App\Console\Commands;

use SmashedEgg\LaravelConsole\Command;

class SayHelloCommand extends Command
{
protected $signature = 'say:hello {name : Name}';

protected $alias = 's:h';

protected $description = 'Say hello to someone';

public function handle()
{
$this->info('Hello ' .$this->argument('name'));
return 0;
}

public function handleInteract()
{
if ( ! $this->input->getArgument('name')) {
$this->input->setArgument('name', $this->ask('Name ?'));
}
}
}

As you can see, this looks a lot nicer, and fits in better with the Laravel way of things.

Conclusion

In a few simple steps we looked at making your Laravel Console Commands interactive, using 2 different approaches.

Hopefully this will help some developers out.

--

--

Tom Ellis

PHP and JavaScript hacker. Symfony and Laravel tinkerer. Open source developer.