Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.89% covered (warning)
88.89%
24 / 27
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
NotificationDispatcher
88.89% covered (warning)
88.89%
24 / 27
75.00% covered (warning)
75.00%
3 / 4
12.20
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 dispatch
85.71% covered (warning)
85.71%
18 / 21
0.00% covered (danger)
0.00%
0 / 1
7.14
 recipientFor
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 render
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace App\Services\Notification;
4
5use App\Jobs\SendNotificationJob;
6
7/**
8 * The one entry point real application code calls — everything else
9 * (which channels are on for this event, credentials, templates) is
10 * resolved from settings so nothing here needs touching when the operator
11 * reconfigures anything. A fresh install has every event's channel list
12 * empty, so dispatch() is a safe no-op until the operator opts an event
13 * into a configured channel via the Notification Settings matrix.
14 */
15class NotificationDispatcher
16{
17    public function __construct(private readonly NotificationManager $manager) {}
18
19    /**
20     * @param  array{email?: string, phone?: string}  $recipients  whichever the enabled channels need
21     * @param  array<string, string|int|float>  $placeholders  values for this event's {{tokens}}
22     */
23    public function dispatch(string $event, array $recipients, array $placeholders): void
24    {
25        $eventConfig = config("notification_events.{$event}");
26
27        if (! $eventConfig) {
28            return;
29        }
30
31        $channels = (array) settings("notifications.{$event}.channels", []);
32        $subjectTemplate = (string) settings("notifications.{$event}.subject", $eventConfig['default_subject']);
33        $bodyTemplate = (string) settings("notifications.{$event}.body", $eventConfig['default_body']);
34
35        foreach ($channels as $channelKey) {
36            if (! $this->manager->has($channelKey) || ! $this->manager->isOn($channelKey)) {
37                continue;
38            }
39
40            if (! $this->manager->driver($channelKey)->isImplemented()) {
41                continue;
42            }
43
44            $to = $this->recipientFor($channelKey, $recipients);
45
46            if (! $to) {
47                continue;
48            }
49
50            SendNotificationJob::dispatch(
51                $event,
52                $channelKey,
53                $to,
54                $this->render($subjectTemplate, $placeholders),
55                $this->render($bodyTemplate, $placeholders),
56            );
57        }
58    }
59
60    private function recipientFor(string $channelKey, array $recipients): ?string
61    {
62        $group = config("notification_channels.{$channelKey}.group");
63
64        return $group === 'Email' ? ($recipients['email'] ?? null) : ($recipients['phone'] ?? null);
65    }
66
67    /** @param  array<string, string|int|float>  $placeholders */
68    public function render(string $template, array $placeholders): string
69    {
70        foreach ($placeholders as $key => $value) {
71            $template = str_replace('{{' . $key . '}}', (string) $value, $template);
72        }
73
74        return $template;
75    }
76}