Creating a window in the Artisan console

Published on Mar 3, 2023

When outputting many lines in an Artisan console command, a user can easily become overwhelmed and lose context. If you feel like this happens with your console output, you could opt to not show output at all. For example, by using a task.

$this->components->task('Installing dependencies', function () {
Process::run('composer install');
});

However, when this takes a long time, it can feel like the command freezes.

To solve this problem, we can create a fixed-height 'window' in the Artisan console. Let's create a window and give it a max height.

$window = $this->output->getOutput()->section();
$window->setMaxHeight($maxHeight);

Since this window is a Symfony Output Section, you could write directly to this using $window->writeln('foo') for example. But it would be ideal if I could continue using the Artisan console methods for output, like $this->line(). To do this, we need to (temporarily) overwrite the output for the whole command. We can even tell Termwind to render its output to this window. That way, we can keep using the render() function to style the command output in the window.

$window = $this->output->getOutput()->section();
$window->setMaxHeight($maxHeight);
 
// Save the original output to restore it later
$originalOutput = $this->output->getOutput();
 
// Overwrite the output of the current command
$this->output = $window;
 
// Tell Termwind to render its output to the window
renderUsing($window); 

After setting this up, we can output as much as we'd like to the console. The window will make sure that the height will not exceed 5 lines.
When you're done with the window, make sure to restore the output to its original state.

$window = $this->output->getOutput()->section();
$window->setMaxHeight($maxHeight);
 
$originalOutput = $this->output->getOutput();
 
$this->output = $window;
renderUsing($window);
 
$this->line('This will be added to the window');
render('<div class="bg-green-500 py-6">This will be added to the window as well!</div>');
 
$this->output = $originalOutput;
renderUsing($originalOutput);

If you want to get the full output that was sent to the window, you can use $window->getContent().

Packing up

To make this reusable and easier to use, I created a trait, so we can create a window like this:

$this->window(title: 'Rick Astley - Never Gonna Give You Up', maxHeight: 5, callable: function () {
 
// ...
 
$this->line('Never gonna give you up');
$this->line('Never gonna let you down');
$this->line('Never gonna run around and desert you');
$this->line('Never gonna make you cry');
$this->line('Never gonna say goodbye');
$this->line('Never gonna tell a lie and hurt you');
 
// ...
 
return true;
});

The eventual output looks like this:

You can find this trait on GitHub, it is also installable as a package if you prefer that.

Do you have feedback or comments? Let me know on Twitter!