Laravel 13 观察者模式深度解析

观察者模式是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知。本文将深入探讨 Laravel 13 中观察者模式的高级用法。

观察者模式基础

什么是观察者模式

观察者模式让一个对象(被观察者)维护一组依赖它的对象(观察者),当状态变化时自动通知它们。

1
2
3
4
5
6
7
8
<?php

namespace App\Contracts;

interface ObserverInterface
{
public function update(mixed $data): void;
}
1
2
3
4
5
6
7
8
9
10
11
12
<?php

namespace App\Contracts;

interface SubjectInterface
{
public function attach(ObserverInterface $observer): void;

public function detach(ObserverInterface $observer): void;

public function notify(): void;
}

Laravel 模型观察者

基础模型观察者

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
120
121
<?php

namespace App\Observers;

use App\Models\User;
use App\Services\AuditService;
use App\Services\NotificationService;
use Illuminate\Support\Facades\Log;

class UserObserver
{
public function __construct(
protected AuditService $audit,
protected NotificationService $notifications
) {}

public function created(User $user): void
{
Log::info('User created', ['user_id' => $user->id]);

$this->audit->log('user.created', [
'user_id' => $user->id,
'email' => $user->email,
]);

$this->notifications->send(
$user->email,
'Welcome!',
'emails.welcome',
['user' => $user]
);
}

public function updated(User $user): void
{
if ($user->wasChanged('email')) {
$this->handleEmailChange($user);
}

if ($user->wasChanged('status')) {
$this->handleStatusChange($user);
}

$this->audit->log('user.updated', [
'user_id' => $user->id,
'changes' => $user->getChanges(),
]);
}

public function deleted(User $user): void
{
Log::info('User deleted', ['user_id' => $user->id]);

$this->audit->log('user.deleted', [
'user_id' => $user->id,
'email' => $user->email,
]);
}

public function restored(User $user): void
{
Log::info('User restored', ['user_id' => $user->id]);
}

public function forceDeleted(User $user): void
{
Log::info('User permanently deleted', ['user_id' => $user->id]);

$this->audit->log('user.force_deleted', [
'user_id' => $user->id,
]);
}

protected function handleEmailChange(User $user): void
{
$user->email_verified_at = null;
$user->save();

$this->notifications->send(
$user->email,
'Email Changed',
'emails.email-changed',
['user' => $user]
);
}

protected function handleStatusChange(User $user): void
{
match($user->status) {
'active' => $this->handleActivation($user),
'suspended' => $this->handleSuspension($user),
'banned' => $this->handleBan($user),
default => null,
};
}

protected function handleActivation(User $user): void
{
$this->notifications->send(
$user->email,
'Account Activated',
'emails.account-activated',
['user' => $user]
);
}

protected function handleSuspension(User $user): void
{
$this->notifications->send(
$user->email,
'Account Suspended',
'emails.account-suspended',
['user' => $user]
);
}

protected function handleBan(User $user): void
{
Log::warning('User banned', ['user_id' => $user->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
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
<?php

namespace App\Observers;

use App\Models\Order;
use App\Events\OrderStatusChanged;
use App\Services\InventoryService;
use App\Services\PaymentService;
use App\Services\ShippingService;
use Illuminate\Support\Facades\Log;

class OrderObserver
{
public function __construct(
protected InventoryService $inventory,
protected PaymentService $payments,
protected ShippingService $shipping
) {}

public function created(Order $order): void
{
Log::info('Order created', [
'order_id' => $order->id,
'order_number' => $order->order_number,
'user_id' => $order->user_id,
]);

$this->reserveInventory($order);
}

public function updated(Order $order): void
{
if ($order->wasChanged('status')) {
$this->handleStatusChange($order);
}
}

protected function handleStatusChange(Order $order): void
{
$oldStatus = $order->getOriginal('status');
$newStatus = $order->status;

Log::info('Order status changed', [
'order_id' => $order->id,
'from' => $oldStatus,
'to' => $newStatus,
]);

event(new OrderStatusChanged($order, $oldStatus, $newStatus));

match($newStatus) {
'paid' => $this->handlePaid($order),
'processing' => $this->handleProcessing($order),
'shipped' => $this->handleShipped($order),
'delivered' => $this->handleDelivered($order),
'cancelled' => $this->handleCancelled($order, $oldStatus),
'refunded' => $this->handleRefunded($order),
default => null,
};
}

protected function handlePaid(Order $order): void
{
$order->update(['paid_at' => now()]);
}

protected function handleProcessing(Order $order): void
{
$this->shipping->createShipment($order);
}

protected function handleShipped(Order $order): void
{
$order->update(['shipped_at' => now()]);
}

protected function handleDelivered(Order $order): void
{
$order->update(['delivered_at' => now()]);
}

protected function handleCancelled(Order $order, string $previousStatus): void
{
$this->releaseInventory($order);

if (in_array($previousStatus, ['paid', 'processing'])) {
$this->payments->refund($order);
}
}

protected function handleRefunded(Order $order): void
{
$this->releaseInventory($order);
$order->update(['refunded_at' => now()]);
}

protected function reserveInventory(Order $order): void
{
foreach ($order->items as $item) {
$this->inventory->reserve($item->product_id, $item->quantity);
}
}

protected function releaseInventory(Order $order): void
{
foreach ($order->items as $item) {
$this->inventory->release($item->product_id, $item->quantity);
}
}
}

注册观察者

在 ServiceProvider 中注册

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

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Models\User;
use App\Models\Order;
use App\Models\Product;
use App\Observers\UserObserver;
use App\Observers\OrderObserver;
use App\Observers\ProductObserver;

class ObserverServiceProvider extends ServiceProvider
{
public function boot(): void
{
User::observe(UserObserver::class);
Order::observe(OrderObserver::class);
Product::observe(ProductObserver::class);
}
}

在模型中注册

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

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Observers\UserObserver;

class User extends Model
{
protected static function boot(): void
{
parent::boot();

static::observe(UserObserver::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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?php

namespace App\Services;

use App\Contracts\ObserverInterface;
use App\Contracts\SubjectInterface;

class EventManager implements SubjectInterface
{
protected array $observers = [];
protected array $events = [];

public function attach(ObserverInterface $observer, array $events = []): void
{
$observerId = spl_object_id($observer);

if (empty($events)) {
$this->observers['*'][$observerId] = $observer;
} else {
foreach ($events as $event) {
$this->observers[$event][$observerId] = $observer;
}
}
}

public function detach(ObserverInterface $observer): void
{
$observerId = spl_object_id($observer);

foreach ($this->observers as $event => $observers) {
unset($this->observers[$event][$observerId]);
}
}

public function notify(string $event, mixed $data = null): void
{
$this->events[] = [
'event' => $event,
'data' => $data,
'timestamp' => now(),
];

$this->notifyObservers($event, $data);
$this->notifyObservers('*', $data);
}

protected function notifyObservers(string $event, mixed $data): void
{
if (!isset($this->observers[$event])) {
return;
}

foreach ($this->observers[$event] as $observer) {
$observer->update([
'event' => $event,
'data' => $data,
]);
}
}

public function getEventHistory(): array
{
return $this->events;
}
}

具体观察者

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

namespace App\Observers;

use App\Contracts\ObserverInterface;
use Illuminate\Support\Facades\Log;

class LoggingObserver implements ObserverInterface
{
protected string $channel;

public function __construct(string $channel = 'default')
{
$this->channel = $channel;
}

public function update(mixed $data): void
{
Log::channel($this->channel)->info('Event occurred', $data);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace App\Observers;

use App\Contracts\ObserverInterface;
use App\Services\MetricService;

class MetricsObserver implements ObserverInterface
{
protected MetricService $metrics;

public function __construct(MetricService $metrics)
{
$this->metrics = $metrics;
}

public function update(mixed $data): void
{
$event = $data['event'] ?? 'unknown';

$this->metrics->increment("events.{$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
<?php

namespace App\Observers;

use App\Contracts\ObserverInterface;
use App\Services\NotificationService;

class NotificationObserver implements ObserverInterface
{
protected NotificationService $notifications;
protected array $recipients;

public function __construct(NotificationService $notifications, array $recipients = [])
{
$this->notifications = $notifications;
$this->recipients = $recipients;
}

public function update(mixed $data): void
{
foreach ($this->recipients as $recipient) {
$this->notifications->send(
$recipient,
"Event: {$data['event']}",
'notifications.event',
$data
);
}
}
}

库存观察者系统

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\Observers\Inventory;

use App\Contracts\ObserverInterface;

class InventoryObserver implements ObserverInterface
{
protected array $thresholds = [];
protected array $notifications = [];

public function setThreshold(int $productId, int $threshold): void
{
$this->thresholds[$productId] = $threshold;
}

public function update(mixed $data): void
{
$productId = $data['product_id'] ?? null;
$quantity = $data['quantity'] ?? 0;

if (!$productId) {
return;
}

if (isset($this->thresholds[$productId])) {
$threshold = $this->thresholds[$productId];

if ($quantity <= $threshold) {
$this->notifications[] = [
'type' => 'low_stock',
'product_id' => $productId,
'quantity' => $quantity,
'threshold' => $threshold,
'timestamp' => now(),
];
}
}
}

public function getNotifications(): array
{
return $this->notifications;
}

public function clearNotifications(): void
{
$this->notifications = [];
}
}
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
<?php

namespace App\Observers\Inventory;

use App\Contracts\ObserverInterface;
use App\Services\EmailService;

class ReorderObserver implements ObserverInterface
{
protected EmailService $email;
protected array $recipients;

public function __construct(EmailService $email, array $recipients)
{
$this->email = $email;
$this->recipients = $recipients;
}

public function update(mixed $data): void
{
if (($data['event'] ?? '') !== 'inventory.low_stock') {
return;
}

foreach ($this->recipients as $recipient) {
$this->email->send(
$recipient,
'Low Stock Alert',
'emails.inventory.low-stock',
$data['data']
);
}
}
}

状态机观察者

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
<?php

namespace App\Services;

use App\Contracts\ObserverInterface;

class StateMachine
{
protected string $currentState;
protected array $transitions = [];
protected array $observers = [];

public function __construct(string $initialState)
{
$this->currentState = $initialState;
}

public function addTransition(string $from, string $to, array $conditions = []): self
{
$this->transitions[$from][$to] = $conditions;
return $this;
}

public function transition(string $to): bool
{
if (!$this->canTransition($to)) {
return false;
}

$from = $this->currentState;
$this->currentState = $to;

$this->notify('state.changed', [
'from' => $from,
'to' => $to,
]);

return true;
}

public function canTransition(string $to): bool
{
return isset($this->transitions[$this->currentState][$to]);
}

public function getCurrentState(): string
{
return $this->currentState;
}

public function attach(ObserverInterface $observer): void
{
$this->observers[spl_object_id($observer)] = $observer;
}

public function detach(ObserverInterface $observer): void
{
unset($this->observers[spl_object_id($observer)]);
}

protected function notify(string $event, mixed $data): void
{
foreach ($this->observers as $observer) {
$observer->update([
'event' => $event,
'data' => $data,
'state' => $this->currentState,
]);
}
}
}

多观察者协调

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
<?php

namespace App\Services;

use App\Contracts\ObserverInterface;

class ObserverChain implements ObserverInterface
{
protected array $observers = [];
protected bool $stopOnFailure = false;

public function add(ObserverInterface $observer): self
{
$this->observers[] = $observer;
return $this;
}

public function stopOnFailure(bool $stop = true): self
{
$this->stopOnFailure = $stop;
return $this;
}

public function update(mixed $data): void
{
foreach ($this->observers as $observer) {
try {
$observer->update($data);
} catch (\Exception $e) {
if ($this->stopOnFailure) {
throw $e;
}
}
}
}
}
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
<?php

namespace App\Services;

use App\Contracts\ObserverInterface;

class PriorityObserverManager
{
protected array $observers = [];

public function attach(ObserverInterface $observer, int $priority = 0): void
{
$this->observers[] = [
'observer' => $observer,
'priority' => $priority,
];

usort($this->observers, fn($a, $b) => $b['priority'] <=> $a['priority']);
}

public function notify(mixed $data): void
{
foreach ($this->observers as $item) {
$item['observer']->update($data);
}
}
}

异步观察者

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

namespace App\Observers;

use App\Contracts\ObserverInterface;
use Illuminate\Support\Facades\Queue;

class AsyncObserver implements ObserverInterface
{
protected string $jobClass;
protected array $observers = [];

public function __construct(string $jobClass)
{
$this->jobClass = $jobClass;
}

public function update(mixed $data): void
{
Queue::push(new $this->jobClass($data));
}
}
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
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ObserverNotificationJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

protected array $data;

public function __construct(array $data)
{
$this->data = $data;
}

public function handle(): void
{
$observers = config('observers.' . $this->data['event'] ?? [], []);

foreach ($observers as $observerClass) {
app($observerClass)->update($this->data);
}
}
}

测试观察者

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
<?php

namespace Tests\Unit\Observers;

use Tests\TestCase;
use App\Models\User;
use App\Observers\UserObserver;
use Illuminate\Support\Facades\Log;
use Mockery;

class UserObserverTest extends TestCase
{
protected UserObserver $observer;

protected function setUp(): void
{
parent::setUp();
$this->observer = new UserObserver(
app(\App\Services\AuditService::class),
app(\App\Services\NotificationService::class)
);
}

public function test_created_logs_and_notifies(): void
{
Log::shouldReceive('info')->once();

$user = User::factory()->make(['id' => 1]);

$this->observer->created($user);

$this->assertDatabaseHas('audit_logs', [
'action' => 'user.created',
]);
}

public function test_updated_detects_email_change(): void
{
$user = User::factory()->create(['email' => 'old@example.com']);
$user->email = 'new@example.com';

$this->observer->updated($user);

$this->assertNull($user->fresh()->email_verified_at);
}

public function test_deleted_logs_deletion(): void
{
Log::shouldReceive('info')->once();

$user = User::factory()->create();

$this->observer->deleted($user);
}
}

最佳实践

1. 观察者应该轻量

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

namespace App\Observers;

use App\Models\User;
use App\Jobs\SendWelcomeEmail;

class UserObserver
{
public function created(User $user): void
{
SendWelcomeEmail::dispatch($user);
}
}

2. 避免循环依赖

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

namespace App\Observers;

use App\Models\User;

class UserObserver
{
public function updated(User $user): void
{
if ($user->isDirty('email')) {
$user->email_verified_at = null;
$user->saveQuietly();
}
}
}

3. 使用事件委托

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

namespace App\Observers;

use App\Models\Order;
use App\Events\{OrderPaid, OrderShipped, OrderCancelled};

class OrderObserver
{
public function updated(Order $order): void
{
if ($order->wasChanged('status')) {
match($order->status) {
'paid' => OrderPaid::dispatch($order),
'shipped' => OrderShipped::dispatch($order),
'cancelled' => OrderCancelled::dispatch($order),
default => null,
};
}
}
}

总结

Laravel 13 的观察者模式提供了一种优雅的方式来实现对象间的松耦合通信。通过合理使用观察者模式,可以创建可维护、可扩展的应用程序架构,同时保持代码的清晰和模块化。