Laravel 13 责任链模式深度解析

责任链模式是一种行为型设计模式,它允许将请求沿着处理链传递,直到有一个处理程序能够处理它。本文将深入探讨 Laravel 13 中责任链模式的高级用法。

责任链模式基础

什么是责任链模式

责任链模式让多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。

1
2
3
4
5
6
7
8
9
10
<?php

namespace App\Contracts;

interface HandlerInterface
{
public function setNext(HandlerInterface $handler): HandlerInterface;

public function handle(mixed $request): mixed;
}

基础处理器实现

抽象处理器

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

namespace App\Handlers;

use App\Contracts\HandlerInterface;

abstract class AbstractHandler implements HandlerInterface
{
protected ?HandlerInterface $next = null;

public function setNext(HandlerInterface $handler): HandlerInterface
{
$this->next = $handler;
return $handler;
}

public function handle(mixed $request): mixed
{
if ($this->next) {
return $this->next->handle($request);
}

return $request;
}
}

审批流程责任链

审批处理器接口

1
2
3
4
5
6
7
8
9
10
<?php

namespace App\Contracts\Approval;

interface ApprovalHandlerInterface
{
public function approve(ApprovalRequest $request): ApprovalResult;

public function setNext(ApprovalHandlerInterface $handler): ApprovalHandlerInterface;
}

具体审批处理器

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\Handlers\Approval;

use App\Contracts\Approval\ApprovalHandlerInterface;
use App\Models\ApprovalRequest;
use App\Models\ApprovalResult;

class TeamLeaderHandler implements ApprovalHandlerInterface
{
protected ?ApprovalHandlerInterface $next = null;
protected float $approvalLimit = 1000;

public function setNext(ApprovalHandlerInterface $handler): ApprovalHandlerInterface
{
$this->next = $handler;
return $handler;
}

public function approve(ApprovalRequest $request): ApprovalResult
{
if ($request->amount <= $this->approvalLimit) {
return new ApprovalResult(
approved: true,
approvedBy: 'Team Leader',
level: 1
);
}

if ($this->next) {
return $this->next->approve($request);
}

return new ApprovalResult(approved: false);
}
}
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\Handlers\Approval;

use App\Contracts\Approval\ApprovalHandlerInterface;
use App\Models\ApprovalRequest;
use App\Models\ApprovalResult;

class ManagerHandler implements ApprovalHandlerInterface
{
protected ?ApprovalHandlerInterface $next = null;
protected float $approvalLimit = 10000;

public function setNext(ApprovalHandlerInterface $handler): ApprovalHandlerInterface
{
$this->next = $handler;
return $handler;
}

public function approve(ApprovalRequest $request): ApprovalResult
{
if ($request->amount <= $this->approvalLimit) {
return new ApprovalResult(
approved: true,
approvedBy: 'Manager',
level: 2
);
}

if ($this->next) {
return $this->next->approve($request);
}

return new ApprovalResult(approved: false);
}
}
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\Handlers\Approval;

use App\Contracts\Approval\ApprovalHandlerInterface;
use App\Models\ApprovalRequest;
use App\Models\ApprovalResult;

class DirectorHandler implements ApprovalHandlerInterface
{
protected ?ApprovalHandlerInterface $next = null;
protected float $approvalLimit = 100000;

public function setNext(ApprovalHandlerInterface $handler): ApprovalHandlerInterface
{
$this->next = $handler;
return $handler;
}

public function approve(ApprovalRequest $request): ApprovalResult
{
if ($request->amount <= $this->approvalLimit) {
return new ApprovalResult(
approved: true,
approvedBy: 'Director',
level: 3
);
}

if ($this->next) {
return $this->next->approve($request);
}

return new ApprovalResult(approved: false);
}
}
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\Handlers\Approval;

use App\Contracts\Approval\ApprovalHandlerInterface;
use App\Models\ApprovalRequest;
use App\Models\ApprovalResult;

class CEOHandler implements ApprovalHandlerInterface
{
protected ?ApprovalHandlerInterface $next = null;

public function setNext(ApprovalHandlerInterface $handler): ApprovalHandlerInterface
{
$this->next = $handler;
return $handler;
}

public function approve(ApprovalRequest $request): ApprovalResult
{
return new ApprovalResult(
approved: true,
approvedBy: 'CEO',
level: 4
);
}
}

构建审批链

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

namespace App\Services;

use App\Handlers\Approval\{TeamLeaderHandler, ManagerHandler, DirectorHandler, CEOHandler};

class ApprovalService
{
protected TeamLeaderHandler $chain;

public function __construct()
{
$teamLeader = new TeamLeaderHandler();
$manager = new ManagerHandler();
$director = new DirectorHandler();
$ceo = new CEOHandler();

$teamLeader->setNext($manager)
->setNext($director)
->setNext($ceo);

$this->chain = $teamLeader;
}

public function processApproval(ApprovalRequest $request): ApprovalResult
{
return $this->chain->approve($request);
}
}

验证责任链

验证处理器

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

namespace App\Handlers\Validation;

use App\Contracts\HandlerInterface;

abstract class ValidationHandler implements HandlerInterface
{
protected ?HandlerInterface $next = null;

public function setNext(HandlerInterface $handler): HandlerInterface
{
$this->next = $handler;
return $handler;
}

public function handle(mixed $request): mixed
{
$error = $this->validate($request);

if ($error !== null) {
return $error;
}

if ($this->next) {
return $this->next->handle($request);
}

return null;
}

abstract protected function validate(mixed $request): ?string;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace App\Handlers\Validation;

class EmailValidationHandler extends ValidationHandler
{
protected function validate(mixed $request): ?string
{
if (!isset($request['email']) || empty($request['email'])) {
return 'Email is required';
}

if (!filter_var($request['email'], FILTER_VALIDATE_EMAIL)) {
return 'Invalid email format';
}

return null;
}
}
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\Handlers\Validation;

class PasswordValidationHandler extends ValidationHandler
{
protected function validate(mixed $request): ?string
{
if (!isset($request['password']) || empty($request['password'])) {
return 'Password is required';
}

if (strlen($request['password']) < 8) {
return 'Password must be at least 8 characters';
}

if (!preg_match('/[A-Z]/', $request['password'])) {
return 'Password must contain at least one uppercase letter';
}

if (!preg_match('/[0-9]/', $request['password'])) {
return 'Password must contain at least one number';
}

return null;
}
}
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\Handlers\Validation;

class UsernameValidationHandler extends ValidationHandler
{
protected function validate(mixed $request): ?string
{
if (!isset($request['username']) || empty($request['username'])) {
return 'Username is required';
}

if (strlen($request['username']) < 3) {
return 'Username must be at least 3 characters';
}

if (!preg_match('/^[a-zA-Z0-9_]+$/', $request['username'])) {
return 'Username can only contain letters, numbers, and underscores';
}

return null;
}
}

中间件责任链

HTTP 中间件链

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\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

abstract class ChainMiddleware
{
protected $next;

public function setNext($middleware): self
{
$this->next = $middleware;
return $this;
}

public function handle(Request $request, Closure $next)
{
$result = $this->process($request);

if ($result !== null) {
return $result;
}

if ($this->next) {
return $this->next->handle($request, $next);
}

return $next($request);
}

abstract protected function process(Request $request);
}
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
<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class AuthenticationMiddleware extends ChainMiddleware
{
protected function process(Request $request)
{
if (!$request->bearerToken()) {
return new JsonResponse(['error' => 'Token not provided'], 401);
}

$token = $request->bearerToken();
$user = $this->validateToken($token);

if (!$user) {
return new JsonResponse(['error' => 'Invalid token'], 401);
}

$request->setUserResolver(fn() => $user);

return null;
}

protected function validateToken(string $token): ?object
{
return \App\Models\User::where('api_token', $token)->first();
}
}
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
<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class AuthorizationMiddleware extends ChainMiddleware
{
protected string $requiredRole;

public function __construct(string $requiredRole = 'user')
{
$this->requiredRole = $requiredRole;
}

protected function process(Request $request)
{
$user = $request->user();

if (!$user) {
return new JsonResponse(['error' => 'Unauthorized'], 403);
}

if (!$user->hasRole($this->requiredRole)) {
return new JsonResponse(['error' => 'Insufficient permissions'], 403);
}

return null;
}
}
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
<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class RateLimitMiddleware extends ChainMiddleware
{
protected int $maxRequests;
protected int $windowSeconds;

public function __construct(int $maxRequests = 60, int $windowSeconds = 60)
{
$this->maxRequests = $maxRequests;
$this->windowSeconds = $windowSeconds;
}

protected function process(Request $request)
{
$key = 'rate_limit:' . $request->ip();
$count = cache()->get($key, 0);

if ($count >= $this->maxRequests) {
return new JsonResponse(['error' => 'Too many requests'], 429);
}

cache()->put($key, $count + 1, $this->windowSeconds);

return null;
}
}

异常处理责任链

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

namespace App\Handlers\Exception;

use Throwable;

interface ExceptionHandlerInterface
{
public function setNext(ExceptionHandlerInterface $handler): ExceptionHandlerInterface;

public function handle(Throwable $exception): ?array;
}
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\Handlers\Exception;

use Throwable;
use Illuminate\Validation\ValidationException;

class ValidationExceptionHandler implements ExceptionHandlerInterface
{
protected ?ExceptionHandlerInterface $next = null;

public function setNext(ExceptionHandlerInterface $handler): ExceptionHandlerInterface
{
$this->next = $handler;
return $handler;
}

public function handle(Throwable $exception): ?array
{
if ($exception instanceof ValidationException) {
return [
'status' => 422,
'message' => 'Validation failed',
'errors' => $exception->errors(),
];
}

if ($this->next) {
return $this->next->handle($exception);
}

return null;
}
}
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
<?php

namespace App\Handlers\Exception;

use Throwable;
use Illuminate\Database\Eloquent\ModelNotFoundException;

class NotFoundExceptionHandler implements ExceptionHandlerInterface
{
protected ?ExceptionHandlerInterface $next = null;

public function setNext(ExceptionHandlerInterface $handler): ExceptionHandlerInterface
{
$this->next = $handler;
return $handler;
}

public function handle(Throwable $exception): ?array
{
if ($exception instanceof ModelNotFoundException) {
return [
'status' => 404,
'message' => 'Resource not found',
];
}

if ($this->next) {
return $this->next->handle($exception);
}

return null;
}
}
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
<?php

namespace App\Handlers\Exception;

use Throwable;
use Illuminate\Auth\AuthenticationException;

class AuthenticationExceptionHandler implements ExceptionHandlerInterface
{
protected ?ExceptionHandlerInterface $next = null;

public function setNext(ExceptionHandlerInterface $handler): ExceptionHandlerInterface
{
$this->next = $handler;
return $handler;
}

public function handle(Throwable $exception): ?array
{
if ($exception instanceof AuthenticationException) {
return [
'status' => 401,
'message' => 'Unauthenticated',
];
}

if ($this->next) {
return $this->next->handle($exception);
}

return null;
}
}

日志处理责任链

1
2
3
4
5
6
7
8
9
10
<?php

namespace App\Handlers\Log;

interface LogHandlerInterface
{
public function setNext(LogHandlerInterface $handler): LogHandlerInterface;

public function handle(string $level, string $message, array $context = []): bool;
}
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 App\Handlers\Log;

class FileLogHandler implements LogHandlerInterface
{
protected ?LogHandlerInterface $next = null;
protected string $path;
protected array $levels = ['error', 'warning', 'info'];

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

public function setNext(LogHandlerInterface $handler): LogHandlerInterface
{
$this->next = $handler;
return $handler;
}

public function handle(string $level, string $message, array $context = []): bool
{
if (in_array($level, $this->levels)) {
$this->writeToFile($level, $message, $context);
}

if ($this->next) {
return $this->next->handle($level, $message, $context);
}

return true;
}

protected function writeToFile(string $level, string $message, array $context): void
{
$timestamp = now()->toIso8601String();
$contextStr = !empty($context) ? json_encode($context) : '';
$line = "[{$timestamp}] {$level}: {$message} {$contextStr}\n";

file_put_contents($this->path, $line, FILE_APPEND);
}
}
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
<?php

namespace App\Handlers\Log;

class SlackLogHandler implements LogHandlerInterface
{
protected ?LogHandlerInterface $next = null;
protected string $webhookUrl;
protected array $levels = ['error', 'critical', 'emergency'];

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

public function setNext(LogHandlerInterface $handler): LogHandlerInterface
{
$this->next = $handler;
return $handler;
}

public function handle(string $level, string $message, array $context = []): bool
{
if (in_array($level, $this->levels)) {
$this->sendToSlack($level, $message, $context);
}

if ($this->next) {
return $this->next->handle($level, $message, $context);
}

return true;
}

protected function sendToSlack(string $level, string $message, array $context): void
{
$payload = [
'text' => "[{$level}] {$message}",
'attachments' => [
[
'color' => $this->getColor($level),
'fields' => collect($context)->map(fn($v, $k) => [
'title' => $k,
'value' => is_array($v) ? json_encode($v) : $v,
'short' => true,
])->values()->toArray(),
],
],
];

Http::post($this->webhookUrl, $payload);
}

protected function getColor(string $level): string
{
return match($level) {
'emergency', 'critical' => 'danger',
'error' => 'warning',
default => 'good',
};
}
}

测试责任链模式

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

namespace Tests\Unit\Handlers;

use Tests\TestCase;
use App\Handlers\Approval\{TeamLeaderHandler, ManagerHandler, DirectorHandler, CEOHandler};
use App\Models\ApprovalRequest;

class ChainOfResponsibilityTest extends TestCase
{
public function test_team_leader_approves_small_amount(): void
{
$chain = $this->createApprovalChain();
$request = new ApprovalRequest(amount: 500);

$result = $chain->approve($request);

$this->assertTrue($result->approved);
$this->assertEquals('Team Leader', $result->approvedBy);
}

public function test_manager_approves_medium_amount(): void
{
$chain = $this->createApprovalChain();
$request = new ApprovalRequest(amount: 5000);

$result = $chain->approve($request);

$this->assertTrue($result->approved);
$this->assertEquals('Manager', $result->approvedBy);
}

public function test_director_approves_large_amount(): void
{
$chain = $this->createApprovalChain();
$request = new ApprovalRequest(amount: 50000);

$result = $chain->approve($request);

$this->assertTrue($result->approved);
$this->assertEquals('Director', $result->approvedBy);
}

public function test_ceo_approves_very_large_amount(): void
{
$chain = $this->createApprovalChain();
$request = new ApprovalRequest(amount: 500000);

$result = $chain->approve($request);

$this->assertTrue($result->approved);
$this->assertEquals('CEO', $result->approvedBy);
}

protected function createApprovalChain(): TeamLeaderHandler
{
$teamLeader = new TeamLeaderHandler();
$manager = new ManagerHandler();
$director = new DirectorHandler();
$ceo = new CEOHandler();

$teamLeader->setNext($manager)
->setNext($director)
->setNext($ceo);

return $teamLeader;
}
}

最佳实践

1. 链条应该可配置

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

class ChainBuilder
{
protected array $handlers = [];

public function add(HandlerInterface $handler): self
{
$this->handlers[] = $handler;
return $this;
}

public function build(): HandlerInterface
{
for ($i = 0; $i < count($this->handlers) - 1; $i++) {
$this->handlers[$i]->setNext($this->handlers[$i + 1]);
}

return $this->handlers[0];
}
}

2. 处理器应该无状态

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

class StatelessHandler implements HandlerInterface
{
public function handle(mixed $request): mixed
{
return $this->process($request);
}

protected function process(mixed $request): mixed
{
return null;
}
}

3. 提供终止条件

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

class TerminatingHandler implements HandlerInterface
{
public function handle(mixed $request): mixed
{
if ($this->shouldTerminate($request)) {
return $this->terminate($request);
}

return $this->next?->handle($request);
}
}

总结

Laravel 13 的责任链模式提供了一种灵活的方式来处理请求。通过合理使用责任链模式,可以创建可扩展、可维护的处理流程,同时保持各处理器的独立性和单一职责。