byrcsc/laravel-approval · 1.x
Notifications.
Enable optional notification delivery, silence or replace any message, and write a notification that declares its own recipients.
Notification delivery is opt-in and disabled by default, so installing the package cannot send external messages and an application's own notification rules stay in charge.
APPROVAL_NOTIFICATIONS_ENABLED=trueWhile it is false, no listeners are registered at all — an application that leaves notifications off pays for none of this.
This page covers which notifications are sent and to whom. To change what they say — rewording, translating, or restructuring the emails — see notification content. Publishing the translations or the mail views needs none of the class replacement described below.
The map
Enabling delivery activates approval.notifications.map:
| Event | Notification | Recipients |
|---|---|---|
ApprovalSubmitted | ApprovalRequestedNotification | The active stage's holders |
StageCompleted | ApprovalRequestedNotification | The newly active stage's holders |
ApprovalApproved | ApprovalApprovedNotification | The requester |
ApprovalRejected | ApprovalRejectedNotification | The requester |
ApprovalReturned | ApprovalReturnedNotification | The requester |
DraftConflicted | ApprovalConflictNotification | The requester |
ApprovalDelegated | ApprovalAssignedNotification | The delegate |
ApproverReassigned | ApprovalAssignedNotification | The new holder |
ApproversResynced | ApprovalAssignedNotification | The holders a resync added |
ApproversEscalated | ApprovalAssignedNotification | The holders escalation added |
StageOverdue | ApprovalOverdueNotification | The overdue stage's holders |
StageOverdueReminder | ApprovalOverdueNotification | The overdue stage's holders |
Not every event is mapped. The mapped ones are the state changes that leave somebody waiting on an action; the rest are available to listen to.
Channels
'notifications' => [
'channels' => ['mail', 'database'],
],The default is ['mail']. The setting applies to every shipped notification;
a replacement class may override via() to choose its own.
Silencing one event
Set its entry to null:
'map' => [
Events\StageOverdueReminder::class => null,
// ...
],Replacing a message
Point the entry at your own class. It must extend ApprovalNotification:
use ByRcsc\LaravelApproval\Events\ApprovalEvent;
use ByRcsc\LaravelApproval\Notifications\ApprovalNotification;
use Illuminate\Notifications\Messages\MailMessage;
final class PurchaseOrderNeedsApproval extends ApprovalNotification
{
public static function recipients(ApprovalEvent $event): iterable
{
return self::activeApprovers($event);
}
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Purchase order awaiting your approval')
->line($this->subject().' needs your sign-off.')
->action('Review', route('purchase-orders.approvals.show', $this->request()));
}
}'map' => [
Events\ApprovalSubmitted::class => PurchaseOrderNeedsApproval::class,
],Notifications declare their own recipients
This is unusual for Laravel and deliberate. Recipients are the part applications most often change, so keeping them on the class means one entry in the map replaces both the message and its audience — no listener to replace.
The base class provides the helpers the shipped notifications use:
self::activeApprovers($event); // holders of the request's active stage
self::approversOf($stage); // holders of a specific stage
self::requesterOf($event); // the requester, as a listAn empty result is valid and sends nothing, which is what happens when a stage's approver records have all been deleted.
Helpers on the instance
$this->event; // the ApprovalEvent
$this->request(); // the ApprovalRequest
$this->approvable(); // the record, or null if deleted since
$this->subject(); // a readable name for the recordsubject() tries name, title, reference, and label on the record,
falling back to ClassName #key, so a subject line always identifies
something. A deleted record reads as "a deleted record".
Delivery is deferred to after commit
Every approval event fires inside a transaction, and a request that is rolled
back a line later should not have already told six people it needs their
approval. The shipped listener wraps the send in DB::afterCommit(). Outside a
transaction the callback runs immediately, so nothing is delayed that does not
need to be.
Configuration is validated at boot
A typo in the map is silent until the day somebody was supposed to be told something, so it is checked when the package boots:
| Problem | Exception |
|---|---|
| The event class does not exist | InvalidConfigurationException::unknownNotificationEvent() |
| The notification class does not exist | InvalidConfigurationException::unknownNotification() |
It does not extend ApprovalNotification | InvalidConfigurationException::notificationIsNotANotification() |
A null entry is not an error — it is how an event is switched off.
Overdue notifications need the sweep
StageOverdue and StageOverdueReminder only fire when approval:escalate
runs. The record_only escalation default sends nothing on its own; enabling
notifications is what turns the recorded fact into a message. See
SLAs and escalation.