8000 [12.x] Dispatch NotificationFailed when sending fails (#55507) · laravel/framework@90dc8f9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 90dc8f9

Browse files
[12.x] Dispatch NotificationFailed when sending fails (#55507)
* dispatch NotificationFailed when sending fails * Update NotificationSender.php --------- Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 5dea84f commit 90dc8f9

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed

src/Illuminate/Notifications/NotificationSender.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
use Illuminate\Contracts\Translation\HasLocalePreference;
77
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
88
use Illuminate\Database\Eloquent\Model;
9+
use Illuminate\Notifications\Events\NotificationFailed;
910
use Illuminate\Notifications\Events\NotificationSending;
1011
use Illuminate\Notifications\Events\NotificationSent;
1112
use Illuminate\Support\Collection;
1213
use Illuminate\Support\Str;
1314
use Illuminate\Support\Traits\Localizable;
15+
use Throwable;
1416

1517
class NotificationSender
1618
{
@@ -44,6 +46,13 @@ class NotificationSender
4446
*/
4547
protected $locale;
4648

49+
/**
50+
* Indicates whether a NotificationFailed event has been dispatched.
51+
*
52+
* @var bool
53+
*/
54+
protected $failedEventWasDispatched = false;
55+
4756
/**
4857
* Create a new notification sender instance.
4958
*
@@ -58,6 +67,8 @@ public function __construct($manager, $bus, $events, $locale = null)
5867
$this->events = $events;
5968
$this->locale = $locale;
6069
$this->manager = $manager;
70+
71+
$this->events->listen(NotificationFailed::class, fn () => $this->failedEventWasDispatched = true);
6172
}
6273

6374
/**
@@ -144,7 +155,19 @@ protected function sendToNotifiable($notifiable, $id, $notification, $channel)
144155
return;
145156
}
146157

147-
$response = $this->manager->driver($channel)->send($notifiable, $notification);
158+
try {
159+
$response = $this->manager->driver($channel)->send($notifiable, $notification);
160+
} catch (Throwable $exception) {
161+
if (! $this->failedEventWasDispatched) {
162+
$this->events->dispatch(
163+
new NotificationFailed($notifiable, $notification, $channel, ['exception' => $exception])
164+
);
165+
}
166+
167+
$this->failedEventWasDispatched = false;
168+
169+
throw $exception;
170+
}
148171

149172
$this->events->dispatch(
150173
new NotificationSent($notifiable, $notification, $channel, $response)

tests/Notifications/NotificationChannelManagerTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22

33
namespace Illuminate\Tests\Notifications;
44

5+use Exception;
56
use Illuminate\Bus\Queueable;
67
use Illuminate\Container\Container;
78
use Illuminate\Contracts\Bus\Dispatcher as Bus;
89
use Illuminate\Contracts\Events\Dispatcher;
910
use Illuminate\Contracts\Queue\ShouldQueue;
1011
use Illuminate\Notifications\ChannelManager;
12+
use Illuminate\Notifications\Events\NotificationFailed;
1113
use Illuminate\Notifications\Events\NotificationSending;
1214
use Illuminate\Notifications\Events\NotificationSent;
1315
use Illuminate\Notifications\Notifiable;
1416
use Illuminate\Notifications\Notification;
1517
use Illuminate\Notifications\SendQueuedNotifications;
18+
use Illuminate\Support\Collection;
1619
use Mockery as m;
1720
use PHPUnit\Framework\TestCase;
1821

@@ -34,6 +37,7 @@ public function testNotificationCanBeDispatchedToDriver()
3437
Container::setInstance($container);
3538
$manager = m::mock(ChannelManager::class.'[driver]', [$container]);
3639
$manager->shouldReceive('driver')->andReturn($driver = m::mock());
40+
$events->shouldReceive('listen')->once();
3741
$events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true);
3842
< 10000 span class=pl-c1>$driver->shouldReceive('send')->once();
3943
$events->shouldReceive('dispatch')->with(m::type(NotificationSent::class));
@@ -49,6 +53,7 @@ public function testNotificationNotSentOnHalt()
4953
$container->instance(Dispatcher::class, $events = m::mock());
5054
Container::setInstance($container);
5155
$manager = m::mock(ChannelManager::class.'[driver]', [$container]);
56+
$events->shouldReceive('listen')->once();
5257
$events->shouldReceive('until')->once()->with(m::type(NotificationSending::class))->andReturn(false);
5358
$events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true);
5459
$manager->shouldReceive('driver')->once()->andReturn($driver = m::mock());
@@ -66,6 +71,7 @@ public function testNotificationNotSentWhenCancelled()
6671
$container->instance(Dispatcher::class, $events = m::mock());
6772
Container::setInstance($container);
6873
$manager = m::mock(ChannelManager::class.'[driver]', [$container]);
74+
$events->shouldReceive('listen')->once();
6975
$events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true);
7076
$manager->shouldNotReceive('driver');
7177
$events->shouldNotReceive('dispatch');
@@ -81,6 +87,7 @@ public function testNotificationSentWhenNotCancelled()
8187
$container->instance(Dispatcher::class, $events = m::mock());
8288
Container::setInstance($container);
8389
$manager = m::mock(ChannelManager::class.'[driver]', [$container]);
90+
$events->shouldReceive('listen')->once();
8491
$events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true);
8592
$manager->shouldReceive('driver')->once()->andReturn($driver = m::mock());
8693
$driver->shouldReceive('send')->once();
@@ -89,6 +96,56 @@ public function testNotificationSentWhenNotCancelled()
8996
$manager->send([new NotificationChannelManagerTestNotifiable], new NotificationChannelManagerTestNotCancelledNotification);
9097
}
9198

99+
public function testNotificationNotSentWhenFailed()
100+
{
101+
$this->expectException(Exception::class);
102+
103+
$container = new Container;
104+
$container->instance('config', ['app.name' => 'Name', 'app.logo' => 'Logo']);
105+
$container->instance(Bus::class, $bus = m::mock());
106+
$container->instance(Dispatcher::class, $events = m::mock());
107+
Container::setInstance($container);
108+
$manager = m::mock(ChannelManager::class.'[driver]', [$container]);
109+
$manager->shouldReceive('driver')->andReturn($driver = m::mock());
110+
$driver->shouldReceive('send')->andThrow(new Exception());
111+
$events->shouldReceive('listen')->once();
112+
$events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true);
113+
$events->shouldReceive('dispatch')->once()->with(m::type(NotificationFailed::class));
114+
$events->shouldReceive('dispatch')->never()->with(m::type(NotificationSent::class));
115+
116+
$manager->send(new NotificationChannelManagerTestNotifiable, new NotificationChannelManagerTestNotification);
117+
}
118+
119+
public function testNotificationFailedDispatchedOnlyOnceWhenFailed()
120+
{
121+
$this->expectException(Exception::class);
122+
123+
$container = new Container;
124+
$container->instance('config', ['app.name' => 'Name', 'app.logo' => 'Logo']);
125+
$container->instance(Bus::class, $bus = m::mock());
126+
$container->instance(Dispatcher::class, $events = m::mock(Dispatcher::class));
127+
Container::setInstance($container);
128+
$manager = m::mock(ChannelManager::class.'[driver]', [$container]);
129+
$manager->shouldReceive('driver')->andReturn($driver = m::mock());
130+
$driver->shouldReceive('send')->andReturnUsing(function ($notifiable, $notification) use ($events) {
131+
$events->dispatch(new NotificationFailed($notifiable, $notification, 'test'));
132+
throw new Exception();
133+
});
134+
$listeners = new Collection();
135+
$events->shouldReceive('until')->with(m::type(NotificationSending::class))->andReturn(true);
136+
$events->shouldReceive('listen')->once()->andReturnUsing(function ($event, $callback) use ($listeners) {
137+
$listeners->push($callback);
138+
});
139+
$events->shouldReceive('dispatch')->once()->with(m::type(NotificationFailed::class))->andReturnUsing(function ($event) use ($listeners) {
140+
foreach ($listeners as $listener) {
141+
$listener($event);
142+
}
143+
});
144+
$events->shouldReceive('dispatch')->never()->with(m::type(NotificationSent::class));
145+
146+
$manager->send(new NotificationChannelManagerTestNotifiable, new NotificationChannelManagerTestNotification);
147+
}
148+
92149
public function testNotificationCanBeQueued()
93150
{
94151
$container = new Container;
@@ -98,6 +155,7 @@ public function testNotificationCanBeQueued()
98155
$bus->shouldReceive('dispatch')->with(m::type(SendQueuedNotifications::class));
99156
Container::setInstance($container);
100157
$manager = m::mock(ChannelManager::class.'[driver]', [$container]);
158+
$events->shouldReceive('listen')->once();
101159

102160
$manager->send([new NotificationChannelManagerTestNotifiable], new NotificationChannelManagerTestQueuedNotification);
103161
}

tests/Notifications/NotificationSenderTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public function testItCanSendQueuedNotificationsWithAStringVia()
3030
$bus = m::mock(BusDispatcher::class);
3131
$bus->shouldReceive('dispatch');
3232
$events = m::mock(EventDispatcher::class);
33+
$events->shouldReceive('listen')->once();
3334

3435
$sender = new NotificationSender($manager, $bus, $events);
3536

@@ -43,6 +44,7 @@ public function testItCanSendNotificationsWithAnEmptyStringVia()
4344
$bus = m::mock(BusDispatcher::class);
4445
$bus->shouldNotReceive('dispatch');
4546
$events = m::mock(EventDispatcher::class);
47+
$events->shouldReceive('listen')->once();
4648

4749
$sender = new NotificationSender($manager, $bus, $events);
4850

@@ -56,6 +58,7 @@ public function testItCannotSendNotificationsViaDatabaseForAnonymousNotifiables(
5658
$bus = m::mock(BusDispatcher::class);
5759
$bus->shouldNotReceive('dispatch');
5860
$events = m::mock(EventDispatcher::class);
61+
$events->shouldReceive('listen')->once();
5962

6063
$sender = new NotificationSender($manager, $bus, $events);
6164

@@ -72,6 +75,7 @@ public function testItCanSendQueuedNotificationsThroughMiddleware()
7275
return $job->middleware[0] instanceof TestNotificationMiddleware;
7376
});
7477
$events = m::mock(EventDispatcher::class);
78+
$events->shouldReceive('listen')->once();
7579

7680
$sender = new NotificationSender($manager, $bus, $events);
7781

@@ -99,6 +103,7 @@ public function testItCanSendQueuedMultiChannelNotificationsThroughDifferentMidd
99103
return empty($job->middleware);
100104
});
101105
$events = m::mock(EventDispatcher::class);
106+
$events->shouldReceive('listen')->once();
102107

103108
$sender = new NotificationSender($manager, $bus, $events);
104109

@@ -122,6 +127,7 @@ public function testItCanSendQueuedWithViaConnectionsNotifications()
122127
});
123128

124129
$events = m::mock(EventDispatcher::class);
130+
$events->shouldReceive('listen')->once();
125131

126132
$sender = new NotificationSender($manager, $bus, $events);
127133

0 commit comments

Comments
 (0)
0