Lean Admin is a Laravel package for building custom admin panels. It's based on the TALLstack (Livewire & Alpine) and its main focus is customizability. Start by defining resources with fields and then customize anything you want. Replace CRUD actions, modify field behavior, or tweak Blade views, and much more. Lean is launching soon, and you can join the the waiting list here to get a discount.

Handling console signals in Laravel

Original โ€“ by Freek Van der Herten โ€“ 7 minute read

When you cancel a long-running artisan command with Ctrl+C, a SIGINT signal is sent by the operating system to the PHP process. You can use this signal to perform some cleanup quickly.

Symfony 5.2 introduced support for handling signals in commands.

We've released a package called spatie/laravel-signal-aware-commands that provides a substantial improvement to how you can use these signals in a Laravel app. In this blog post, I'd like to tell you all about it.

Handling signals in Laravel

As Laravel's artisan commands are based on Symfony, signals can be handled in Laravel as well. You should simply add the SignableCommandInterface and implement the getSubscribedSignals and handleSignal methods.

That's basically all you need to do to make commands signal aware, but it doesn't feel very "Laravel-y" to me.

Ideally, I just want to add a function like onSigint to the command and put the signal handling code there. That feels much lighter.

// in an artisan command class

protected $signature = 'your-command';

public function handle()
{
    $this->info('Command started...');

    sleep(100);
}

public function onSigint()
{
    // will be executed when you stop the command

    $this->info('You stopped the command!');
}

Something that I found lacking in the code that Symfony provides is that only the command itself is aware of signals. Wouldn't it be nice if we could handle signals anywhere in the app, like this?

Signal::handle(SIGINT, function() {
   // handle the signal
});

This seemed like a nice thing for me to work on, so I decided to create a package that would add these niceties.

In this stream, you can see me build the foundations of spatie/laravel-signal-aware-command

Using Signal aware commands

Making commands signal aware is easy. When the package is installed, there are three ways to handle signals:

  • on the command itself
  • via the Signal facade
  • using the SignalReceived event

Let's start with making a command signal aware. You need to let your command extend SignalAwareCommand. Next, define a method that starts with on followed by the name of the signal. Here's an example where the SIGINT signal is handled.

use Spatie\SignalAwareCommand\SignalAwareCommand;

class YourCommand extends SignalAwareCommand
{
    protected $signature = 'your-command';

    public function handle()
    {
        $this->info('Command started...');

        sleep(100);
    }

    public function onSigint()
    {
        // will be executed when you stop the command
    
        $this->info('You stopped the command!');
    }
}

The above code will make the command signal aware. But what if you want to handle signals in other parts of your code? In that case, you can use the Signal facade. First, you need to define the signals you want to handle in your command in the handlesSignals property.

use Spatie\SignalAwareCommand\SignalAwareCommand;

class YourCommand extends SignalAwareCommand
{
    protected $signature = 'your-command';
    
    protected $handlesSignals = [SIGINT];

    public function handle()
    {
        (new SomeOtherClass())->performSomeWork();

        sleep(100);
    }
}

In any class you'd like, you can use the Signal facade to register code that should be executed when a signal is received.

use Illuminate\Console\Command;
use Spatie\SignalAwareCommand\Facades\Signal;

class SomeOtherClass
{
    public function performSomeWork()
    {
        Signal::handle(SIGINT, function(Command $commandThatReceivedSignal) {
            $commandThatReceivedSignal->info('Received the SIGINT signal!');
        })
    }
}

You can call clearHandlers if you want to remove a handler that was previously registered.

use Spatie\SignalAwareCommand\Facades\Signal;

public function performSomeWork()
{
    Signal::handle(SIGNINT, function() {
        // perform cleanup
    });
    
    $this->doSomeWork();
    
    // at this point doSomeWork was executed without any problems
    // running a cleanup isn't necessary anymore
    Signal::clearHandlers(SIGINT);
}

To clear all handlers for all signals use Signal::clearHandlers().

A third way of handling signal is by listening for the SignalReceived event.

use Spatie\SignalAwareCommand\Events\SignalReceived;
use Spatie\SignalAwareCommand\Signals;

class SomeOtherClass
{
    public function performSomeWork()
    {
        Event::listen(function(SignalReceived $event) {
            $signalNumber = $event->signal;
            
            $signalName = Signals::getSignalName($signalNumber);
        
            $event->command->info("Received the {$signalName} signal");
        });
    }
}

I hope you agree that these three ways of handling signals feels much better than adding the SignableCommandInterface and implement the getSubscribedSignals and handleSignal yourself.

A practical example

To see a practical example of this package, can take a look at this commit in the laravel-backup package.

The laravel-backup package creates a backup in the form of a zip file that contains DB dumps and a selection of files. This zip is created inside of a temporary directory. Should the user cancel a running backup command, we'll not use the SIGINT signal to delete that temporary directory.

Pretty nice, right?

Question: why haven't you PR'ed this to Laravel?

After creating the package, some people have asked why I didn't immediately PR this to Laravel. The answer is that creating a package is much faster for me to do. When creating a package, I basically have a blank canvas. I can implement functionality in whichever fashion I want. I can also make use of PHP 8 features. When the package is finished, I can immediately tag a release and start using it.

If I would PR this to Laravel, I would keep Laravel's coding practices in mind, use an older version of PHP. The PR would probably be in review for a couple of days without the guarantee of actually being merged.

The above paragraph isn't a criticism on Laravel. It's just the way it is. It's good that things are getting review and that there is a discussion on features that are added.

I hope that by creating a package, somebody from the community or the Laravel team takes the time to PR some of the functionality to Laravel, making my package obsolete.

Until that happens, you can use my package.

Question: why are you letting users extend a class

Right now, the package requires you to extend the SignalAwareCommand command instead of the regularCommand. Some people frown upon this, and rightly so. Putting functionality in base classes is some cases not flexible, and in the Laravel world, it seems that use traits is preferred. Generally speaking, I prefer this too, as a class can only be extended from one other class, but you can have as many traits as you want.

In this case, I opted for the package offering an abstract SignalAwareCommand that should be extended instead of a trait because this way, other parts of the code could be hidden.

Let's imagine the package would offer a trait instead of a base class. This is would people would have to use it.

class TestCommand extends Command
{
    use HandlesSignals;
    
    public function __construct()
    {
        parent::construct();
        
        // signals must be known when the command is constructed
        $this->registerSignalsToBeHandled();
    }
    
    public function handle()
    {
        $this->registerSignalHandlers();
        
        // your code
    }
}

In my mind, this would undermine the better DX that the package tries to achieve.

I bet that if the ideas of this package make it into Laravel, that the necessary changes could be added directly into Illuminate\Console\Command, making DX even better.

In closing

To know more about spatie/laravel-signal-aware-command, head over the the readme on GitHub.

This isn't the first package our team has created. Here's a list of packages we released previously. I'm pretty sure there's something there for your next project.

If you want to support our team creating open-source, consider becoming a sponsor, or picking up one of our paid products.

Stay up to date with all things Laravel, PHP, and JavaScript.

Follow me on Twitter. I regularly tweet out programming tips, and what I myself have learned in ongoing projects.

Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

Webmentions

Martin Medina liked on 9th August 2021
Reno Philibert liked on 8th August 2021
Camiant liked on 8th August 2021
Alexandru Bucur liked on 8th August 2021
Benjamin Crozat liked on 7th August 2021
Lloric Mayuga Garcia liked on 7th August 2021
Hardik Shah liked on 7th August 2021
Florian Wartner liked on 7th August 2021
Eelco Luurtsema liked on 7th August 2021
Stefan Bauer liked on 7th August 2021
Ed Grosvenor liked on 7th August 2021
Ahmet MirzabeyoฤŸlu liked on 7th August 2021
Robin Dirksen liked on 7th August 2021
Stefan Bauer replied on 7th August 2021
Whoop Whoop ๐Ÿ™Œ
Rafael Agostini liked on 7th April 2021
Elias liked on 6th April 2021
Mohammad Nur liked on 6th April 2021
evdalll liked on 6th April 2021
Fazal e Rabbi retweeted on 6th April 2021
Fazal e Rabbi liked on 6th April 2021
smknstd liked on 6th April 2021
Joan Morell liked on 6th April 2021
MiraCool retweeted on 6th April 2021
Spatie retweeted on 6th April 2021
mahmod liked on 6th April 2021
Phil Rowley liked on 6th April 2021
Gennaro Landolfi liked on 6th April 2021
Uziel Bueno liked on 6th April 2021
Pragnesh Chauhan liked on 6th April 2021
David Adi Nugroho liked on 6th April 2021
Jake Casto liked on 6th April 2021
repat liked on 6th April 2021
repat retweeted on 6th April 2021
Ramon H. Ornelas liked on 6th April 2021
Onno Lissenberg liked on 6th April 2021
Mark Szymik ๐Ÿ”ฅ replied on 6th April 2021
Check again ;)
Wrong in the screenshot, corrected in the actual post ๐Ÿ‘
Mark Szymik ๐Ÿ”ฅ replied on 6th April 2021
so Symfony or Symphony? :)
Niko Halink liked on 6th April 2021
Burhan liked on 5th April 2021
Oskar Stark liked on 5th April 2021
Nandor Sperl liked on 5th April 2021
ali ali liked on 5th April 2021
์•ˆํ˜•์šฐ retweeted on 5th April 2021
Matthijs Hoekstra retweeted on 5th April 2021
Ante Esang liked on 5th April 2021
Ashish Srivastava liked on 5th April 2021
Yannick Yayo liked on 5th April 2021
Padam Shankhadev liked on 5th April 2021
Vikas Kapadiya liked on 5th April 2021
Bhargav Nanekalva retweeted on 5th April 2021
Onur Uslu liked on 5th April 2021
Towhid liked on 5th April 2021
Hunter Skrasek liked on 5th April 2021
Sajan Rajbhandari liked on 5th April 2021
Willan Correia liked on 5th April 2021
Richard liked on 5th April 2021
theonlyub liked on 5th April 2021
Johannes Pichler liked on 5th April 2021
Ruslan liked on 5th April 2021
easternsoul retweeted on 5th April 2021
easternsoul liked on 5th April 2021
Nazmul Hasan Robin liked on 5th April 2021
Maarten de Graaf retweeted on 5th April 2021
Chris Fidao retweeted on 5th April 2021
Tom Schlick liked on 5th April 2021
Travis Elkins liked on 5th April 2021
Niels Vanpachtenbeke liked on 5th April 2021
Paras Malhotra liked on 5th April 2021
๐ŸŸฃ Michael Brooks liked on 5th April 2021
Maarten de Graaf liked on 5th April 2021
Farjam liked on 5th April 2021
Matt Stauffer liked on 5th April 2021
Chris Cachor liked on 5th April 2021
Jagruti Metaliya liked on 5th April 2021
Claudio_Pereira liked on 5th April 2021
eraldgreca liked on 5th April 2021
Homestead Ted replied on 5th April 2021
Thanks!
tldr liked on 5th April 2021
สวษนpuษ liked on 5th April 2021
Homestead Ted replied on 5th April 2021
Cool. I didn't even know this was possible at the application level. Are there any other console signals or is ctrl-c the only one?
Nuno Maduro replied on 5th April 2021
tutorialspoint.com/unix/unix-signโ€ฆ
Nuno Maduro retweeted on 5th April 2021
Nuno Maduro liked on 5th April 2021
Brenier Arnaud liked on 5th April 2021
Lucas Fiege liked on 5th April 2021
Josรฉ Cage liked on 5th April 2021
Julien Bourdeau liked on 5th April 2021
Josรฉ Cage retweeted on 5th April 2021
Felipe ๐Ÿ• liked on 5th April 2021
Wyatt liked on 5th April 2021