PHP

★ Handling console signals in Laravel

[AdSense-A]

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.

Symphony 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 Symphony, 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(<span class="hljs-number">100</span>);

}

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

<span class="hljs-keyword">$this</span>-&gt;info(<span class="hljs-string">'You stopped the command!'</span>);

}

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 SpatieSignalAwareCommandSignalAwareCommand;

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

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">$this</span>-&gt;info(<span class="hljs-string">'Command started...'</span>);

    sleep(<span class="hljs-number">100</span>);
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onSigint</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-comment">// will be executed when you stop the command</span>

    <span class="hljs-keyword">$this</span>-&gt;info(<span class="hljs-string">'You stopped the command!'</span>);
}

}

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<span class="hljs-title">SignalAwareCommand<span class="hljs-title">SignalAwareCommand;

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

<span class="hljs-keyword">protected</span> $handlesSignals = [SIGINT];

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">()</span>
</span>{
    (<span class="hljs-keyword">new</span> SomeOtherClass())-&gt;performSomeWork();

    sleep(<span class="hljs-number">100</span>);
}

}

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<span class="hljs-title">Console<span class="hljs-title">Command;
use Spatie<span class="hljs-title">SignalAwareCommand<span class="hljs-title">Facades<span class="hljs-title">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<span class="hljs-title">SignalAwareCommand<span class="hljs-title">Facades<span class="hljs-title">Signal;

public function performSomeWork() { Signal::handle(SIGNINT, function() { // perform cleanup });

<span class="hljs-keyword">$this</span>-&gt;doSomeWork();

<span class="hljs-comment">// at this point doSomeWork was executed without any problems</span>
<span class="hljs-comment">// running a cleanup isn't necessary anymore</span>
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<span class="hljs-title">SignalAwareCommand<span class="hljs-title">Events<span class="hljs-title">SignalReceived;
use Spatie<span class="hljs-title">SignalAwareCommand<span class="hljs-title">Signals;

class SomeOtherClass { public function performSomeWork() { Event::listen(function(SignalReceived $event) { $signalNumber = $event->signal;

        $signalName = Signals::getSignalName($signalNumber);
    
        $event-&gt;command-&gt;info(<span class="hljs-string">"Received the {$signalName} signal"</span>);
    });
}

}

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;
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">parent</span>::construct();
    
    <span class="hljs-comment">// signals must be known when the command is constructed</span>
    <span class="hljs-keyword">$this</span>-&gt;registerSignalsToBeHandled();
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-keyword">$this</span>-&gt;registerSignalHandlers();
    
    <span class="hljs-comment">// your code</span>
}

}

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 IlluminateConsoleCommand, 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.

(more…)

By , ago
PHP

Improving Ignition's security

A few days ago, you might have received a Dependabot security warning on Ignition concerning a remote code execution vulnarablity. This post on the Flare blog explains why most people shouldn’t be effected by this. Read more…

By , ago
PHP

Vite with Laravel

Vite is a frontend build tool like webpack. Instead of bundling development assets, Vite serves native ES modules transpiled with esbuild from the dev server. This means there’s a lot less bundling to do, and Read more…

By , ago
PHP

★ Mailcoach v4 has released: drip campaigns, automations, mail templates and much more

[AdSense-A]

I'm proud to announce that Mailcoach v4 has been released. Mailcoach already was a great solution to send out bulk emails affordably. With an entirely refreshed UI and new capabilities, Mailcoach now becomes a more powerful platform for all things email:

We've also rewritten our extensive documentation.

In this blog post, I'd like to give you a tour of everything Mailcoach can do.

A high-level overview

If you want to see a high-level overview of what Mailcoach can do, check out this video.

Mailcoach can be either be installed into an existing Laravel app as a package or used as a standalone application.

When installing Mailcoach into a Laravel app as a package, you can tightly integrate Mailcoach into your application logic. You can listen for the various events that Mailcoach fires when emails are opened, clicked, ... to execute custom logic. Mailcoach stores information in the database using regular Eloquent models that can be used by your application code too.

Sending regular email campaigns

When launching Mailcoach, sending email campaigns was the main feature. Mailcoach uses reliable email sending services such as Amazon SES, Mailgun, and a few more... to send out emails. Using these services is, in many cases, much cheaper than using a service like Mailchimp.

In this video, you'll see a demo of sending a campaign.

My newsletter, which you can subscribe to on this page, is powered by Mailcoach.

Automating emails and drip campaigns

Mailcoach v4 now supports the #1 requested feature: email automation. Mailcoach now has a beautiful UI for creating email flows. Want to create an email course where subscribers daily get the next lesson of your course, no problem! Want to send a mail an hour after somebody has clicked a particular link in a previous mail? Mailcoach can handle it.

In this video, you'll see a demo of this new powerful feature.

That screen where you can chain and nest your flow's different actions is entirely built with Livewire.

Handling transactional mails

Mailcoach v4 gains support for handling transactional emails. This means you can track opens and clicks of emails sent by your Laravel application or even resend emails from within Mailcoach.

Don't worry about email presentation anymore in your code: team members without any technical knowledge can now also manage the transactional email templates with their editor of choice.

All you need to do in your code is to either use the StoresMail or UsesMailcoachTemplate in your Mailable.

Here's another video, where you'll see me prepare and use a mailable with those traits.

Building Mailcoach

In the past, you've seen me and my colleagues publish various posts on the development of Mailcoach. For convenience, I'll list some of there here.

In closing

Mailcoach v4 is big release. We're very proud of it. This package is probably the biggest one our team has worked on.

I want to thank my colleague Rias for all the fantastic work he did around automations; it's truly remarkable work. Willem also did an outstanding job refreshing the whole UI. All members of our team provided feedback and helped with testing here and there.

Since the initial launch, we got a lot of positive feedback from our customers who bought Mailcoach. Some of them reported that they could easily send emails to lists that contain more than 500 000 subscribers. Of course, we're using Mailcoach ourselves too at Spatie, freek.dev and Oh Dear.

Want to know more about Mailcoach, then head over to the extensive documentation.

To celebrate the launch of v4, you can now, for a limited time, purchase Mailcoach with a 20% discount.

(more…)

By , ago