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