各位下午好,欢迎来到这场名为“别让你的业务逻辑变成意大利面”的讲座。
坐在台下的各位,我想问个直击灵魂的问题:你们写过多少个“类似项目”?
是不是那种情况:老板说“我们要做一个电商后台”,你用 Laravel 搞定了;半年后,老板说“我们要做一个客户管理系统”,你顺手又拿出了 Laravel;再过半年,老板突发奇想,我们要做一个内部 API 网关,结果你发现,虽然底层的 CRUD 逻辑差不多,但你不得不重新造轮子,因为这次客户坚持要用 Slim,而团队里还有个 CodeIgniter 的死忠粉。
这就是我们面临的“框架围城”。每个框架都有自己的一套中间件机制,一套认证逻辑,一套日志系统。你为了追求开发速度,把 Auth::check() 写在 Laravel 里,把 use Auth; 写在 Slim 里。结果呢?当你想把这些代码复用到另一个项目时,发现它们像是一坨粘连在一起的生面团,怎么扯都扯不开。
今天,我们就来聊聊如何通过中间件(Middleware)模式,打破这些框架的围墙,建立一套跨框架的业务逻辑标准化体系。
我们不聊虚的,直接上干货。在此之前,请想象一下你是一家连锁餐厅的后厨经理。
第一章:厨房里的“服务员”——什么是中间件?
在 PHP 的世界里,中间件是什么?很多人说它是洋葱。洋葱有两层意思:一层是核心逻辑,一层是包在周围的层层包裹。每次请求进来,都要经过一系列的“安检”和“清洗”,最后才能见到核心的“厨师”(你的 Controller)。
如果把你写的业务逻辑比作“做牛排”,那么中间件就是:
- 入单服务员:确认这单是谁点的(身份验证)。
- 收银员:看这人能不能吃这牛排(权限验证)。
- 摆盘师:把牛排端上去之前,摆个盘(日志记录)。
关键点来了: 摆盘师(中间件)不管牛排熟没熟(业务逻辑),他只管摆盘。如果你把“检查牛排熟度”的逻辑写在摆盘师的代码里,那就是屎山代码。
第二章:PSR-15——我们的通用语言
PHP 之所以能流行,除了语法简单,还有一个很重要的原因:标准化。但早期框架五花八门,导致中间件接口也是五花八门。Laravel 有自己的,Symfony 有自己的,Slim 又有自己的。
为了解决这个问题,PHP-FIG 组织祭出了PSR-15(HTTP Server Request Handler)标准。这玩意儿就像是“世界语”。
PSR-15 规定了两个接口:
- MiddlewareInterface:所有中间件都要实现这个接口。它有三个方法:
process(Request, RequestHandler)。 - RequestHandlerInterface:它代表“下一个要处理的环节”,或者“处理完所有中间件后的最终响应对象”。
一旦你实现了这两个接口,你的中间件就是“独立宣言”的签署者。它不再依赖任何框架。它只依赖一个事实:输入是一个 Request,输出必须是一个 Response,中间可以做任何事。
让我们来看一段标准的中间件代码。这段代码在任何框架下都能跑,只要它遵循 PSR-7(HTTP 消息接口)和 PSR-15。
<?php
namespace AppMiddleware;
use PsrHttpMessageResponseInterface as Response;
use PsrHttpMessageServerRequestInterface as Request;
use PsrHttpServerRequestHandlerInterface as RequestHandler;
use PsrHttpServerMiddlewareInterface as Middleware;
class CrossOriginMiddleware implements Middleware
{
public function process(Request $request, RequestHandler $handler): Response
{
// 1. 这里的逻辑是通用的,不关心你后面是 Laravel 还是 Slim
$response = $handler->handle($request);
// 2. 标准化 CORS 处理
return $response
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
}
}
看懂了吗?这段代码里没有 $app->get,没有 Route::,没有 Auth::guard。它只处理 HTTP 协议层面的通用问题。这就是标准化的第一步:隔离框架代码与通用代码。
第三章:跨框架的“适配器模式”
光有中间件还不够。我们还需要解决一个更深层的问题:业务逻辑依赖。
业务逻辑里通常离不开数据库操作、Redis 缓存、发送邮件。这些操作在不同框架里的写法天差地别。
- Laravel 里你可能用
DB::table('users')->get()。 - Slim 里你可能直接用 PDO。
- CodeIgniter 里你可能用
$this->db->query()。
如果你把直接调用数据库的逻辑写进了中间件,那你的中间件就“死”了,因为它被框架特定的 ORM 绑定死了。
专家建议:中间件只做“流程控制”,不做“数据操作”。
我们要引入一个抽象层,或者叫“适配器层”。这个层负责把框架特定的数据库连接,转变成一个通用的接口。
假设我们有一个通用的 UserService,它不关心底层的 PDO 是怎么来的,也不关心是 Laravel 的 DB 还是 Doctrine。
namespace AppServices;
class UserService
{
// 我们注入一个接口,而不是具体的类
private $userRepository;
public function __construct(UserRepositoryInterface $userRepository)
{
$this->userRepository = $userRepository;
}
public function getCurrentUser()
{
// 这里的业务逻辑是标准的,不管底层怎么实现的
$userId = $_SESSION['user_id'] ?? 0;
return $this->userRepository->find($userId);
}
}
然后,我们在框架配置文件里,把这个框架特定的 Repository 实现注入进去。
- Laravel 配置:注入
IlluminateDatabaseEloquentModel的实现。 - Slim 配置:注入一个
PDO查询的封装类。
这样一来,你的中间件只需要:
public function process(Request $request, RequestHandler $handler): Response
{
$userService = new UserService($container->get('user_repo'));
$user = $userService->getCurrentUser();
// 继续处理...
$response = $handler->handle($request);
return $response;
}
看,中间件变得非常干净。它只负责调用 UserService,而 UserService 才是真正干活的。这就是关注点分离的最高境界。
第四章:实战演练——构建一个“通用日志中间件”
为了让你们彻底理解,我们来实战写一个日志中间件。这玩意儿通常每个框架都要写一遍,而且写法千奇百怪。让我们把它标准化。
需求: 记录请求的 URI、耗时、以及用户 ID。无论在哪个框架,格式要一样,日志路径要一样。
标准化中间件:
<?php
namespace AppMiddleware;
use PsrHttpMessageResponseInterface as Response;
use PsrHttpMessageServerRequestInterface as Request;
use PsrHttpServerRequestHandlerInterface as RequestHandler;
use PsrHttpServerMiddlewareInterface as Middleware;
class AuditLoggingMiddleware implements Middleware
{
// 我们需要注入一个通用的 Logger 接口,而不是框架特定的 Log facade
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function process(Request $request, RequestHandler $handler): Response
{
$startTime = microtime(true);
// 在请求开始前记录
$this->logger->info('Request Start', [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'ip' => $request->getServerParams()['REMOTE_ADDR'] ?? 'unknown'
]);
try {
// 交给下一个处理器(可能是路由,也可能是下一个中间件)
$response = $handler->handle($request);
// 计算耗时
$duration = round((microtime(true) - $startTime) * 1000, 2);
// 在请求结束后记录(带上状态码)
$this->logger->info('Request End', [
'status_code' => $response->getStatusCode(),
'duration' => $duration . 'ms'
]);
return $response;
} catch (Exception $e) {
// 异常处理也要标准化
$duration = round((microtime(true) - $startTime) * 1000, 2);
$this->logger->error('Request Failed', [
'exception' => get_class($e),
'message' => $e->getMessage(),
'duration' => $duration . 'ms'
]);
// 抛出异常让框架层处理,或者返回标准的错误响应
throw $e;
}
}
}
注意到了吗?这个中间件里,完全没有任何 $app->middleware 或者 Route::middleware 的痕迹。它只依赖 PSR-15 接口和 PSR-3 (Logger Interface)。
你只需要在 Laravel 的 bootstrap/app.php 里注册它,在 Slim 的 AppFactory 里注册它,在 CodeIgniter 的 Middleware 类里实现它。但逻辑本身,是永久可复用的。
第五章:中间件的“洋葱”堆叠与分组
当你的系统变大了,你需要一层又一层的中间件。认证 -> 权限 -> 日志 -> 限流 -> 性能分析。
这时候,中间件的“管道模式”就非常重要了。PSR-15 标准很好地支持了这种堆叠。
业务逻辑标准化中的陷阱:
千万不要在中间件里写复杂的业务判断!
- 错误:
if ($user->role == 'admin') { ... }—— 这是业务逻辑,应该在 Controller 或 Service 里。 - 正确:
if ($user->hasPermission('admin_panel')) { ... }—— 这是流程控制,属于中间件的职责。
我们可以写一个“权限检查中间件”,它不关心权限的具体定义,它只关心:这个人是否有资格通过这道门?
namespace AppMiddleware;
use AppServicesPermissionService;
class RoleBasedAccessMiddleware implements Middleware
{
private $permissionService;
public function __construct(PermissionService $permissionService)
{
$this->permissionService = $permissionService;
}
public function process(Request $request, RequestHandler $handler): Response
{
// 1. 获取当前请求的路由信息(这部分不同框架处理方式不同,需要适配)
$route = $request->getAttribute('route');
// 2. 定义该路由需要的权限
$requiredRole = $route->getOption('require_role'); // 假设我们从路由配置里拿
if (!$requiredRole) {
// 如果路由没要求,直接放行
return $handler->handle($request);
}
// 3. 检查用户权限
if (!$this->permissionService->hasRole($requiredRole)) {
// 标准化返回 403 Forbidden
$response = new SlimPsr7Response();
$response->getBody()->write(json_encode(['error' => 'Forbidden']));
return $response->withStatus(403);
}
return $handler->handle($request);
}
}
注意到了吗?中间件处理了“用户没有权限怎么办”的流程,但是具体的“权限怎么查”、“角色有哪些”,都在 PermissionService 里。
这种模式允许你在不同框架间移植时,只移植中间件的注册代码,而不需要移植任何业务逻辑代码。
第六章:处理多框架下的“生命周期”差异
这可能是最难的一点。PHP 是无状态的,但在不同框架里,HTTP 请求的生命周期差异巨大。
- Laravel:有
App::booted,有Kernel,请求周期长,有很多全局钩子。 - Slim:非常轻量,生命周期短,基本就是 Request -> Handler -> Response。
- Swoole/Workerman:长连接,进程常驻内存。
如果你要把业务逻辑标准化,你必须把这些“框架特性”封装掉。
比如,有一个“限流(Rate Limiting)”中间件。
在 Laravel 里,你可能需要每分钟保存 N 个请求到 Redis。
在 Slim 里,你可能需要每分钟保存 N 个请求到 Redis。
在 Swoole 里,你可能需要每个进程保存,并且要考虑多进程锁。
标准化方案:
中间件只负责记录“我是谁,我想干嘛”。至于“我是怎么存数据的”,交给底层的 StorageAdapter。
public function process(Request $request, RequestHandler $handler): Response
{
$ip = $request->getServerParams()['REMOTE_ADDR'];
// 这里的 key 是通用的,不管你怎么存
$this->counter->increment('api_request_count', $ip);
$limit = $this->config->get('rate_limit');
$current = $this->counter->get('api_request_count', $ip);
if ($current > $limit) {
return $this->createErrorResponse('Too Many Requests');
}
return $handler->handle($request);
}
对于 Swoole,StorageAdapter 可能是共享内存中的计数器;对于 Slim,它可能就是 Redis。业务逻辑(限流算法)是不变的,只有存储介质变了。
第七章:不要让中间件变成“上帝类”
在结束之前,我要给你们泼一盆冷水。很多新手在实现标准化时,会犯一个错误:把所有逻辑都塞进中间件。
他们写了一个 UniversalMiddleware,里面包含了:
- 解析 JWT。
- 解析 SOAP。
- 查询用户。
- 格式化响应。
- 记录 SQL 慢查询。
- 检查 HTTPS。
这不行!这绝对是反模式。
专家级设计原则:单一职责。
中间件应该只做一件事,或者几件非常相关的事。我们要把 UniversalMiddleware 拆解:
AuthenticationMiddleware:只管鉴权,返回 User 对象。BodyParserMiddleware:只管把 JSON 解析成对象。JsonResponseMiddleware:只管把对象序列化成 JSON。PerformanceMonitorMiddleware:只管计时。
在你的业务逻辑代码(Service 层)里,把这些中间件串联起来。这样,当你在 Laravel 里复用这段代码到 Slim 时,你可以自己组合需要的中间件,而不需要买一送一堆你用不上的逻辑。
第八章:最终架构图解(脑补版)
想象一下你的系统架构:
- 最底层:框架层。Laravel 的 Kernel, Slim 的 Middleware, CodeIgniter 的 Hooks。它们就像房子的地基,形状各异,但都用来支撑房子。
- 中间层:标准接口层。也就是我们刚才写的那些实现 PSR-15 的中间件。
- 应用层:业务逻辑层。也就是你的 Service,你的 Repository,你的 Domain Logic。这是房子的主体。
- 最上层:框架适配器。负责把框架特定的对象,转换成标准接口层的对象。
流程是这样的:
HTTP 请求进来 -> 框架层处理 -> 抛给标准接口层中间件 -> 标准接口层调用应用层 -> 应用层处理 -> 返回给标准接口层 -> 返回给框架层。
你看,中间层(标准接口层)和应用层(业务逻辑层)是互不认识的,它们甚至都不知道对方是谁。它们只知道通过“接口”对话。这就是为什么它们可以跨框架移植。
第九章:为什么这是终极解决方案?
你可能问:“直接用 PHP 纯原生写不就行了吗?干嘛还要中间件?”
纯原生写法当然可以,但那是 2010 年的做法。现代 PHP 开发强调的是组合和解耦。
使用中间件模式标准化,带来的好处是显而易见的:
- 降本增效:开发新项目时,直接把去年的中间件库复制过来,甚至不用改一行代码。你节省的是每天重复造轮子的时间。
- 团队协作:如果你的团队里,有人负责 Laravel,有人负责 Slim,但他们要维护同一套业务规则(比如支付逻辑、会员体系),中间件模式让你们有了共同语言。Laravel 开发者写的中间件,Slim 开发者可以直接用。
- 测试友好:标准的中间件是可以单独测试的。你可以写一个单元测试,传入一个 Mock Request,看看中间件返回的 Response 是否符合预期。这比测试一个庞大的 Controller 要简单得多。
第十章:代码重构指南——如何迁移你的屎山
最后,我们来看一个具体的重构步骤。
假设你现在有一堆写在 Laravel Controller 里的业务逻辑,比如一个 UserController。里面有 500 行代码,混杂了 DB 查询、Email 发送、权限判断、HTML 输出。
第一步:提取业务逻辑。
把核心的“获取用户列表”逻辑提取到 UserListService 里。这个 Service 不依赖任何框架,只依赖标准接口。
第二步:创建中间件。
创建一个 UserListMiddleware。
- 在中间件里,初始化
UserListService。 - 调用 Service 获取数据。
- 把数据存入 Request 的 Attribute 里,方便后续读取。
第三步:迁移。
在你的 Slim 项目里,引入相同的 UserListMiddleware。
你会发现,Slim 的 Controller 变得非常简单:
public function list(Request $request, Response $response) {
// 数据已经在中间件里准备好了,直接取就行
$users = $request->getAttribute('users');
$response->getBody()->write(json_encode($users));
return $response;
}
原本那个 500 行的 Laravel Controller,现在变成了两个 100 行的文件。这就是重构的艺术。
结语:拥抱通用,拒绝锁死
各位,PHP 生态系统之所以强大,是因为它既有灵活性的“泥巴”,又有标准化的“水泥”。
中间件模式就是我们用来砌墙的水泥。它让我们的业务逻辑像砖块一样,可以在不同的框架之间自由搬运、拼接、堆叠。
不要让你的代码被某个框架绑架。当你写下一行 new Request(),或者实现一个 process 方法时,请保持一种心态:我是一个跨平台的通用组件,我是为了解决业务问题而存在的,而不是为了解决框架问题。
当你做到了这一点,你就是一名真正的 PHP 架构师。哪怕你明天被老板逼着从 Laravel 换到 Swoole,你也能淡定地喝口咖啡,说一句:“呵,无非就是换个中间件注册的地方。”
好了,今天的讲座就到这里。下面是提问环节,我不接受“这代码能不能跑”这种问题,我只接受“如何把它集成到 .NET Core 里”这种挑战性的问题。谢谢大家!