Laravel 13 订阅者模式提供了组织多个事件监听器的优雅方式,本文介绍如何实现订阅者模式。

订阅者基础

基础订阅者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

namespace App\Subscribers;

use Illuminate\Events\Dispatcher;

abstract class BaseSubscriber
{
protected array $listeners = [];

public function subscribe(Dispatcher $events): void
{
foreach ($this->listeners as $event => $listeners) {
foreach ((array) $listeners as $listener) {
$events->listen($event, $listener);
}
}
}

public function unsubscribe(Dispatcher $events): void
{
foreach ($this->listeners as $event => $listeners) {
foreach ((array) $listeners as $listener) {
$events->forget($event);
}
}
}
}

业务订阅者

订单订阅者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?php

namespace App\Subscribers;

use App\Events\OrderCreated;
use App\Events\OrderUpdated;
use App\Events\OrderCancelled;
use App\Events\OrderShipped;
use App\Events\OrderDelivered;
use Illuminate\Events\Dispatcher;

class OrderSubscriber
{
public function subscribe(Dispatcher $events): array
{
return [
OrderCreated::class => [
'handleOrderCreated',
],
OrderUpdated::class => [
'handleOrderUpdated',
],
OrderCancelled::class => [
'handleOrderCancelled',
],
OrderShipped::class => [
'handleOrderShipped',
],
OrderDelivered::class => [
'handleOrderDelivered',
],
];
}

public function handleOrderCreated(OrderCreated $event): void
{
$this->updateInventory($event->order);
$this->sendConfirmation($event->order);
$this->trackAnalytics('order_created', $event->order);
}

public function handleOrderUpdated(OrderUpdated $event): void
{
$this->notifyChanges($event->order, $event->changes);
$this->logChanges($event->order, $event->changes);
}

public function handleOrderCancelled(OrderCancelled $event): void
{
$this->restoreInventory($event->order);
$this->processRefund($event->order);
$this->sendCancellationNotice($event->order);
}

public function handleOrderShipped(OrderShipped $event): void
{
$this->sendShippingNotification($event->order);
$this->updateTracking($event->order);
}

public function handleOrderDelivered(OrderDelivered $event): void
{
$this->sendDeliveryConfirmation($event->order);
$this->requestReview($event->order);
}

protected function updateInventory($order): void
{
}

protected function sendConfirmation($order): void
{
}

protected function trackAnalytics($event, $order): void
{
}

protected function notifyChanges($order, $changes): void
{
}

protected function logChanges($order, $changes): void
{
}

protected function restoreInventory($order): void
{
}

protected function processRefund($order): void
{
}

protected function sendCancellationNotice($order): void
{
}

protected function sendShippingNotification($order): void
{
}

protected function updateTracking($order): void
{
}

protected function sendDeliveryConfirmation($order): void
{
}

protected function requestReview($order): void
{
}
}

用户订阅者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<?php

namespace App\Subscribers;

use App\Events\UserRegistered;
use App\Events\UserUpdated;
use App\Events\UserDeleted;
use App\Events\UserLoggedIn;
use App\Events\UserLoggedOut;
use Illuminate\Events\Dispatcher;

class UserSubscriber
{
public function subscribe(Dispatcher $events): array
{
return [
UserRegistered::class => [
'handleUserRegistered',
],
UserUpdated::class => [
'handleUserUpdated',
],
UserDeleted::class => [
'handleUserDeleted',
],
UserLoggedIn::class => [
'handleUserLoggedIn',
],
UserLoggedOut::class => [
'handleUserLoggedOut',
],
];
}

public function handleUserRegistered(UserRegistered $event): void
{
$this->sendWelcomeEmail($event->user);
$this->createUserProfile($event->user);
$this->assignDefaultRole($event->user);
$this->trackRegistration($event->user);
}

public function handleUserUpdated(UserUpdated $event): void
{
$this->syncExternalServices($event->user);
$this->auditChanges($event->user, $event->changes);
}

public function handleUserDeleted(UserDeleted $event): void
{
$this->anonymizeData($event->user);
$this->cancelSubscriptions($event->user);
$this->notifyAdmin($event->user);
}

public function handleUserLoggedIn(UserLoggedIn $event): void
{
$this->updateLastLogin($event->user);
$this->checkSecurityAlerts($event->user);
$this->trackSession($event->user);
}

public function handleUserLoggedOut(UserLoggedOut $event): void
{
$this->cleanupSession($event->user);
}

protected function sendWelcomeEmail($user): void
{
}

protected function createUserProfile($user): void
{
}

protected function assignDefaultRole($user): void
{
}

protected function trackRegistration($user): void
{
}

protected function syncExternalServices($user): void
{
}

protected function auditChanges($user, $changes): void
{
}

protected function anonymizeData($user): void
{
}

protected function cancelSubscriptions($user): void
{
}

protected function notifyAdmin($user): void
{
}

protected function updateLastLogin($user): void
{
}

protected function checkSecurityAlerts($user): void
{
}

protected function trackSession($user): void
{
}

protected function cleanupSession($user): void
{
}
}

支付订阅者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<?php

namespace App\Subscribers;

use App\Events\PaymentInitiated;
use App\Events\PaymentCompleted;
use App\Events\PaymentFailed;
use App\Events\RefundProcessed;
use Illuminate\Events\Dispatcher;

class PaymentSubscriber
{
public function subscribe(Dispatcher $events): array
{
return [
PaymentInitiated::class => [
'handlePaymentInitiated',
],
PaymentCompleted::class => [
'handlePaymentCompleted',
],
PaymentFailed::class => [
'handlePaymentFailed',
],
RefundProcessed::class => [
'handleRefundProcessed',
],
];
}

public function handlePaymentInitiated(PaymentInitiated $event): void
{
$this->logPaymentAttempt($event->payment);
$this->validatePayment($event->payment);
$this->notifyUser($event->payment, 'initiated');
}

public function handlePaymentCompleted(PaymentCompleted $event): void
{
$this->updateOrderStatus($event->payment);
$this->sendReceipt($event->payment);
$this->updateUserBalance($event->payment);
$this->trackRevenue($event->payment);
}

public function handlePaymentFailed(PaymentFailed $event): void
{
$this->logFailure($event->payment, $event->error);
$this->notifyUser($event->payment, 'failed');
$this->alertSupport($event->payment, $event->error);
}

public function handleRefundProcessed(RefundProcessed $event): void
{
$this->updateOrderStatus($event->refund);
$this->sendRefundNotification($event->refund);
$this->adjustRevenue($event->refund);
}

protected function logPaymentAttempt($payment): void
{
}

protected function validatePayment($payment): void
{
}

protected function notifyUser($payment, string $status): void
{
}

protected function updateOrderStatus($payment): void
{
}

protected function sendReceipt($payment): void
{
}

protected function updateUserBalance($payment): void
{
}

protected function trackRevenue($payment): void
{
}

protected function logFailure($payment, $error): void
{
}

protected function alertSupport($payment, $error): void
{
}

protected function sendRefundNotification($refund): void
{
}

protected function adjustRevenue($refund): void
{
}
}

订阅者注册

服务提供者注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

namespace App\Providers;

use App\Subscribers\OrderSubscriber;
use App\Subscribers\UserSubscriber;
use App\Subscribers\PaymentSubscriber;
use Illuminate\Foundation\Support\Providers\EventServiceProvider;

class SubscriberServiceProvider extends EventServiceProvider
{
protected $subscribe = [
OrderSubscriber::class,
UserSubscriber::class,
PaymentSubscriber::class,
];
}

动态订阅者

动态订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php

namespace App\Services\Subscribers;

use Illuminate\Events\Dispatcher;

class DynamicSubscriberManager
{
protected Dispatcher $dispatcher;
protected array $subscribers = [];

public function __construct(Dispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}

public function register(string $subscriberClass): void
{
$subscriber = app($subscriberClass);

$events = $subscriber->subscribe($this->dispatcher);

$this->subscribers[$subscriberClass] = $events;
}

public function unregister(string $subscriberClass): void
{
if (!isset($this->subscribers[$subscriberClass])) {
return;
}

foreach ($this->subscribers[$subscriberClass] as $event => $listeners) {
foreach ($listeners as $listener) {
$this->dispatcher->forget($event);
}
}

unset($this->subscribers[$subscriberClass]);
}

public function getSubscribers(): array
{
return array_keys($this->subscribers);
}

public function hasSubscriber(string $subscriberClass): bool
{
return isset($this->subscribers[$subscriberClass]);
}
}

条件订阅者

条件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php

namespace App\Subscribers;

use App\Events\OrderCreated;
use Illuminate\Events\Dispatcher;

class ConditionalOrderSubscriber
{
protected array $conditions = [];

public function __construct()
{
$this->conditions = [
'handleHighValueOrder' => fn($event) => $event->order->total > 1000,
'handleInternationalOrder' => fn($event) => $event->order->is_international,
'handleSubscriptionOrder' => fn($event) => $event->order->has_subscription,
];
}

public function subscribe(Dispatcher $events): array
{
return [
OrderCreated::class => [
'handleOrderCreated',
],
];
}

public function handleOrderCreated(OrderCreated $event): void
{
foreach ($this->conditions as $method => $condition) {
if ($condition($event) && method_exists($this, $method)) {
$this->{$method}($event);
}
}
}

protected function handleHighValueOrder(OrderCreated $event): void
{
}

protected function handleInternationalOrder(OrderCreated $event): void
{
}

protected function handleSubscriptionOrder(OrderCreated $event): void
{
}
}

订阅者测试

订阅者测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

namespace Tests\Subscribers;

use App\Events\OrderCreated;
use App\Subscribers\OrderSubscriber;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

class OrderSubscriberTest extends TestCase
{
protected OrderSubscriber $subscriber;

protected function setUp(): void
{
parent::setUp();

$this->subscriber = new OrderSubscriber();
}

public function test_it_subscribes_to_order_events()
{
$events = $this->subscriber->subscribe(app('events'));

$this->assertArrayHasKey(OrderCreated::class, $events);
}

public function test_it_handles_order_created_event()
{
$user = User::factory()->create();
$order = Order::factory()->create(['user_id' => $user->id]);

$event = new OrderCreated($order, $user);

$this->subscriber->handleOrderCreated($event);

$this->assertDatabaseHas('inventory_logs', [
'order_id' => $order->id,
]);
}
}

订阅者监控

监控服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

namespace App\Services\Subscribers;

class SubscriberMonitor
{
protected array $stats = [];

public function recordExecution(string $subscriber, string $method, float $duration): void
{
$key = "{$subscriber}::{$method}";

if (!isset($this->stats[$key])) {
$this->stats[$key] = [
'count' => 0,
'total_duration' => 0,
];
}

$this->stats[$key]['count']++;
$this->stats[$key]['total_duration'] += $duration;
}

public function getStats(): array
{
return array_map(function ($stat) {
return [
'count' => $stat['count'],
'avg_duration' => $stat['count'] > 0
? $stat['total_duration'] / $stat['count']
: 0,
];
}, $this->stats);
}
}

总结

Laravel 13 订阅者模式提供了组织多个事件监听器的优雅方式。通过将相关事件处理逻辑集中在一个类中,可以提高代码的可维护性和可读性。