Laravel DeferrableProvider not working

Title was a bit clickbaity but perhaps this will save someone else some time.

Put simply, the ‘register’ method on a service provider class is always called when executing from a php artisan context. This is not explained anywhere in the service provider documentation. The ‘Deferred Providers‘ section of the documentation does not explicitly call this out, and it cost me some time last night debugging an issue.

*WHY* this is, I’m not quite sure, but it can certainly cause some confusion as to why code operates one way via http requests and one way via console execution. If/when I have time I may add a bit more to this, but just dropping this note here for now…

Laravel prevent duplicate emails

On a recent project I misread a ticket and understood it as a need to prevent duplicate emails being sent (within a certain amount of time). I’d realized the ticket was calling for something different, but I finished some investigation on what it would take to prevent potentially accidental duplicate sends.

My first thought was to write a plugin for the swift_mailer component, which … I did, but then realized it may be easier to hook in to the event system at the Laravel level (vs down lower in the mailer plugin level).

I ended up adding an event listener in EventServiceProvider.php

protected $listen = [
'Illuminate\Mail\Events\MessageSending' => [
'App\Listeners\PreventDuplicateEmail',
],
];


The PreventDuplicateEmail class

namespace App\Listeners;

use Illuminate\Mail\Events\MessageSending;
use Swift_Message;

class PreventDuplicateEmail
{
static $done = [];

public function handle(MessageSending $event)
{
$message = $event->message;
$hash = $this->hash($message);
$toRecipients = $message->getHeaders()->getAll("to")[0]->getNameAddresses();
foreach($toRecipients as $email=>$name)
{
if(!array_key_exists($email.$hash, self::$done))
{
self::$done[$email.$hash] = time();
}
}
}

public function hash(Swift_Message $message)
{
$subject = $message->getSubject();
$body = $message->getBody();
$from = array_key_first($message->getFrom());
$combined = $subject.$body.$from;
return substr($subject, 0,20).":".substr($from,0,20).":".sha1($combined);
}

}

This isn’t finished – to prevent sending, use a ‘return false;’ in the listener. And determining the window – 5 minutes? 1 hour? – that’d be up to you. You could store everything in a database table and check there as well. Because I’d misread the requirements, I didn’t completely finish this, but hopefully this gets you a bit closer if this is what you were looking for…