PHP 专家级设计思考:论如何通过中间件模式实现跨多框架的业务逻辑标准化与物理隔离

PHP 专家级设计思考:论如何通过中间件模式实现跨多框架的业务逻辑标准化与物理隔离

大家好,欢迎来到今天的“拯救混乱代码”特别讲座。我是你们的主讲人,一个在这个 PHP 混乱江湖里摸爬滚打、从“面条式代码”中幸存下来的老油条。

今天我们不谈配置,不谈 Composer 版本,我们谈点更血淋淋的——架构

假设一下,你现在接手了一个项目。打开文件夹,你看到的不是一串有序的代码,而是一幅抽象派的地图。左边是 Laravel 写的订单接口,中间夹着几块 Symfony 的老代码,右边不知道是谁塞进来的一个 WordPress 插件,甚至还有一坨 Zend Framework 的旧物,它们混在一起,就像把红烧肉、麻辣烫和螺蛳粉放在一起搅拌。

这就是现实。这就是为什么很多 PHP 工程师头上开始有白头发的原因。我们常说“不要重复造轮子”,但现在的问题是,这帮人根本没在造轮子,他们只是在把别人的轮子用胶带粘在一起,然后宣称这是“架构”。

那么,如何从这堆泥巴里挖出金条?答案就在我们的手上——中间件

但不是那种用来检查登录态的中间件,那是小儿科。我们要讲的是,如何用中间件构建一道物理隔离墙,把业务逻辑从烂框架里抽离出来,让它在任何地方都能像瑞士军刀一样好用。

第一章:痛,并快乐着的“混合大法”

首先,我们得直面人性。为什么我们会用这么多框架?

理由千奇百怪。老板说:“用 Laravel 做新系统,因为它快。” 研发主管说:“那个支付接口是两年前用 Zend 写的,换框架成本太高,改改吧。” 程序员小王说:“我用 Symfony 写了个组件,挺好用的,要不集成进来?”

结果呢?结果就是你的代码变成了意大利面。

为什么?因为每个框架都有自己独特的“味道”。Laravel 喜欢用 Service Container,Symfony 喜欢用 Dependency Injection Component,Slim 喜欢极简,WordPress 喜欢全局变量和钩子。

如果你的业务逻辑直接写死在控制器里,那你实际上是在写框架的依赖项。一旦框架升级,或者你想换个框架,对不起,你得把你业务逻辑里那 500 行代码全部重写。这不仅是痛苦,这是对生命力的谋杀。

我们的目标是什么?

我们要打造一个独立的业务内核。这个内核不知道也不在乎它跑在 Laravel 上,还是跑在 WordPress 里,甚至是跑在命令行(CLI)里。它只需要知道怎么处理一个“订单”,至于怎么接收 HTTP 请求?那是中间件的事。

第二章:中间件——HTTP 的管道工

很多人对中间件的理解停留在“登录检查”上。没错,那是入门级用法。在专家眼里,中间件是流控阀,是过滤器,是适配器的集合体。

想象一下,HTTP 请求就像一列开往用户浏览器的火车。中间件模式就是车站。第一站:参数校验;第二站:身份验证;第三站:权限检查;第四站:业务逻辑处理;最后:返回响应。

关键在于,业务逻辑只存在于最后一站

为什么这样设计?因为中间件是可堆叠的。你可以把它想象成俄罗斯套娃,也可以想象成洋葱——只不过这洋葱没让你哭,而是让你更清爽。

在这个架构中,我们引入两个核心标准,这是 PHP 界的“普通话”,大家得学会:

  1. PSR-7:定义了 HTTP 消息接口。Request 是什么?Response 是什么?不管是 Symfony 的 HttpFoundation 还是 Zend Diactoros,它们都得实现这两个接口。这就是我们的“物理隔离”基础。
  2. PSR-15:定义了中间件接口。这玩意儿就是我们的“交换协议”。

第三章:定义标准——接口是人类的契约

我们要做的第一步,就是造一个契约。如果你不做接口,代码就是一张废纸。

让我们定义一个简单的 RequestHandlerInterface。这东西不需要依赖任何框架,它只负责干一件事:处理业务。

namespace AppCoreKernel;

use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;

/**
 * 业务处理器的契约
 * 这是一个没有腿的鸟,我们要把它装在任何笼子里
 */
interface RequestHandlerInterface
{
    /**
     * 处理请求并返回响应
     * @param ServerRequestInterface $request
     * @return ResponseInterface
     */
    public function handle(ServerRequestInterface $request): ResponseInterface;
}

看到了吗?没有 use IlluminateHttpRequest,没有 use SymfonyComponentHttpFoundationRequest。只有标准。这就是物理隔离的第一步:解耦

第四章:中间件工厂——胶水代码的艺术

现在,我们有一个业务核心(比如叫 OrderServiceKernel)。它很纯洁,它只包含下单的逻辑,比如扣除库存、创建记录、发送邮件。它甚至不关心钱是不是真实的。

但是,这个 OrderServiceKernel 需要一个入口。谁来启动它?谁来把 Laravel 的 IlluminateHttpRequest 转换成一个标准的 PSR-7 ServerRequestInterface 传给它?

就是中间件工厂

我们写一个工厂类,它的任务就是连接“框架”和“业务”。

namespace AppInfrastructureAdapter;

use AppCoreKernelRequestHandlerInterface;
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;

/**
 * 框架适配器层
 * 我们可以在这里创建一个通用的中间件管道
 */
class FrameworkMiddlewareAdapter
{
    /**
     * @var RequestHandlerInterface 核心业务逻辑
     */
    private $coreHandler;

    public function __construct(RequestHandlerInterface $coreHandler)
    {
        $this->coreHandler = $coreHandler;
    }

    /**
     * 创建一个 PSR-15 中间件
     * 这个中间件接收框架的请求,转换后扔给核心逻辑
     */
    public function createAdapterMiddleware(): callable
    {
        return function (ServerRequestInterface $request, callable $next) {
            // 这里是“物理隔离”的关卡。
            // 如果我们想,这里可以做额外的框架级处理,
            // 比如记录日志、监控等,但这不会污染业务代码。

            try {
                // 1. 调用核心业务逻辑
                $response = $this->coreHandler->handle($request);
            } catch (Throwable $e) {
                // 2. 如果业务逻辑抛出异常,这里统一捕获
                // 注意:不要在这里直接 echo,要构造 Response
                $response = $this->createErrorResponse($e);
            }

            return $response;
        };
    }

    private function createErrorResponse(Throwable $e): ResponseInterface
    {
        // 这里可以简单处理,或者交给专门的错误处理中间件
        // 返回一个 JSON 错误,假装一切都好
        // 实际上,我们要保证业务代码永远不产生 HTML 输出
    }
}

你看,这个适配器是万能的。你把 Laravel 的 Request 放进来,它塞给核心;你把 Symfony 的 Request 放进来,它照样塞给核心。

第五章:实战演示——Laravel 场景

好,理论太枯燥,我们来看看怎么在 Laravel 里“捣乱”(指正确地整合)。

假设你已经把业务逻辑封装到了 AppCoreKernelOrderProcessor 里。

在 Laravel 中,中间件是在 bootstrap/app.php 或者路由里定义的。

// bootstrap/app.php

use IlluminateHttpRequest;
use IlluminateContractsHttpKernel as HttpKernelContract;
use AppCoreKernelRequestHandlerInterface;
use AppInfrastructureAdapterFrameworkMiddlewareAdapter;

$app = new LaravelApplication($_ENV['APP_BASE_PATH'] ?? dirname(__DIR__));

// 1. 绑定核心逻辑到容器
// 这样我们就可以在任何地方通过依赖注入拿到它
$app->singleton(RequestHandlerInterface::class, function ($app) {
    // 这里可以是你的领域服务实例
    return new AppCoreKernelOrderProcessor(
        $app->make(AppRepositoriesOrderRepositoryInterface::class)
    );
});

// 2. 创建适配器
$adapter = new FrameworkMiddlewareAdapter($app->make(RequestHandlerInterface::class));
$kernel = $app->make(HttpKernelContract::class);

// 3. 在路由中间件中使用
return $app->router->group(function (Router $router) use ($adapter) {
    $router->post('/checkout', function (Request $request) use ($adapter) {
        // 这里的魔法发生了:
        // Laravel 的 Request 需要被“洗白”成 PSR-7 请求
        $psr7Request = $request->toPsrMessage();

        // 调用适配器,执行我们的核心逻辑
        $response = $adapter->createAdapterMiddleware()($psr7Request, function($r) {
            // 理论上不会走到这里,因为核心逻辑会直接返回 Response
        });

        // 把 PSR-7 响应转回 Laravel 响应
        return $response;
    });
});

这段代码干净吗?是的。它把核心逻辑藏得很深,而路由层只负责“翻译”和“调用”。如果以后老板说“换回 CodeIgniter”,你只需要改 bootstrap/app.php,业务代码 OrderProcessor 一个字都不用动!

第六章:实战演示——Symfony/Slim 场景

同样的架构,换个壳子,效果依然惊艳。

在 Slim 中,这简直是量身定做的。Slim 的哲学就是中间件优先。

use PsrHttpMessageResponseInterface as Response;
use PsrHttpMessageServerRequestInterface as Request;
use SlimFactoryAppFactory;
use AppCoreKernelRequestHandlerInterface;
use AppInfrastructureAdapterFrameworkMiddlewareAdapter;

// 初始化 Slim
$app = AppFactory::create();

// 绑定核心逻辑
// 这里不需要容器,你可以直接实例化,或者用简单的 DI
$coreHandler = new AppCoreKernelOrderProcessor(/*...*/);
$adapter = new FrameworkMiddlewareAdapter($coreHandler);

$app->add(function (Request $request, $next) use ($adapter) {
    // 这里的 $request 已经是 PSR-7 了,太完美了!
    // 直接扔给核心处理器
    return $adapter->createAdapterMiddleware()($request, $next);
});

$app->post('/checkout', function (Request $request, Response $response) {
    // 注意:如果核心处理器抛出异常,这里可能会被捕获或未捕获
    // 所以通常建议把中间件逻辑提取出来,或者让 Slim 的 ErrorMiddleware 处理
});

$app->run();

看到了吗?在 Symfony 里,你需要配置 HttpKernelInterface,配置 EventDispatcher,配置各种 Bean。而在 Slim 里,我们直接把代码塞进中间件堆里。

这就是标准化的力量。

第七章:业务逻辑的“物理隔离”与“领域模型”

现在,我们的 OrderProcessor 可以是任何样子了。

它不应该是一个巨大的、塞满了 if-else 的怪兽。它应该是一个充血的领域模型

namespace AppCoreKernel;

use AppExceptionsOutOfStockException;

class OrderProcessor implements RequestHandlerInterface
{
    private $orderRepo;
    private $inventoryService;

    public function __construct(OrderRepositoryInterface $orderRepo, InventoryServiceInterface $inventoryService)
    {
        $this->orderRepo = $orderRepo;
        $this->inventoryService = $inventoryService;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        // 1. 获取数据
        $data = $request->getParsedBody();
        $productId = $data['product_id'] ?? null;

        // 2. 领域行为 - 这里的代码不应该有框架痕迹
        if (!$this->inventoryService->checkStock($productId)) {
            throw new OutOfStockException("没货了,别买了");
        }

        // 3. 创建订单
        $order = new Order($productId);
        $this->orderRepo->save($order);

        // 4. 扣减库存
        $this->inventoryService->decreaseStock($productId);

        // 5. 返回成功响应
        return new JsonResponse([
            'status' => 'success',
            'order_id' => $order->getId()
        ]);
    }
}

看这段代码。如果上面有 $this->db,那就是死路一条。如果上面有 DB::table('orders'),那就是移植噩梦。

这段代码是纯净的。它只关心订单、库存、钱。它不关心是 HTTP 协议,也不关心是不是 Web 访问。

当你拥有了这种物理隔离,你就可以随意移植。你想用 PHP CLI 写个脚本,给库存打折?直接实例化 OrderProcessor,从文件或数据库读数据,扔进去处理。你甚至可以在 PHP 内部写个微服务,让 Python 调用这个类的 JSON RPC 接口。框架?框架只是它的一层皮肤。

第八章:进阶——处理“脏”数据与日志

你可能会问:“如果框架传过来的数据是烂的怎么办?”

这就是中间件的价值所在。在中间件管道的最前端,也就是框架适配器之前,我们可以加一个“清洗中间件”。

class DirtyDataCleanerMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // 这里可以做一些框架特定的转换
        // 比如:把 Laravel 的 Input 转换为 PSR-7 Query/Body
        // 比如:把 Symfony 的 ParameterBag 解析出来

        // 还可以做数据清洗
        $cleaned = $this->sanitize($request->getParsedBody());

        // 这里比较 trick,因为我们不能直接改 $request 对象(除非它是可变的,但 PSR-7 推荐 不可变)
        // 实际上,我们通常在适配器层处理,或者使用一个中间件来包装 Request

        return $handler->handle($request);
    }
}

通常,我们在 FrameworkMiddlewareAdapter 里做这件事。这是最合适的地方。因为适配器负责“翻译”,翻译的第一步就是把“方言”变成“普通话”。

此外,日志中间件是必不可少的。不要在业务逻辑里写 file_put_contents。写一个 LoggingMiddleware,它只是拦截请求,打个印,然后扔过去。业务代码永远不知道日志存在。

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
    $startTime = microtime(true);

    // 记录请求开始
    $this->logger->info("Request started", ['path' => $request->getUri()]);

    $response = $handler->handle($request);

    $duration = microtime(true) - $startTime;

    // 记录请求结束
    $this->logger->info("Request completed", [
        'duration' => $duration . 's',
        'status' => $response->getStatusCode()
    ]);

    return $response;
}

第九章:为什么这能解决“团队冲突”?

这不仅仅是个技术问题,这是个社会学问题。

你的团队里可能有 Laravel 专家,有 Symfony 派,还有个老顽固死守着 CodeIgniter。以前,如果他们要合作开发一个模块,那简直就是世界大战。

现在,有了中间件标准:

  1. Laravel 队友负责写 bootstrap/app.php 和路由,然后调用我们的 OrderProcessor
  2. Symfony 队友负责写 Kernel.php,然后调用我们的 OrderProcessor
  3. CI 队友负责写 CLI 脚本,直接调用 OrderProcessor

大家写的都是同一个类。这就是统一战线。这就是通过代码规范实现团队协作的终极奥义。

第十章:不要在中间件里做业务

这点非常重要,我必须大声吼出来。

中间件是穿鞋的,业务逻辑是

鞋(中间件)可以有很多种:登山鞋、皮鞋、运动鞋、拖鞋。但脚(业务逻辑)只有一双。你不能把脚穿进拖鞋里,也不能把脚塞进登山鞋里。

如果你发现你在中间件里写 if ($user->isAdmin) { ... },去删了它。这是业务逻辑。如果它在中间件里,你就被框架束缚住了。你应该在 OrderProcessor 里注入一个 UserContext 服务,让它自己判断。

中间件只做三件事:

  1. 预处理:参数格式化、身份识别。
  2. 流转:传递请求。
  3. 后处理:日志、监控、压缩。

除此之外,不要碰。

第十一章:物理隔离的极限

有些老派架构师会说:“直接用数据库驱动,谁还用中间件?”

亲爱的,数据库驱动也是中间件的一种,它是最底层的。你现在的中间件是 HTTP 中间件,是应用层的。你是在你的业务层和数据库层之间,强行插入了一层抽象。

如果有一天,你的业务量暴涨,你需要从 MySQL 迁移到 PostgreSQL,或者从 PHP 迁移到 Java 微服务。如果你的业务逻辑是直接写在 Controller 里的,那你得重写 5000 行代码。

如果你的业务逻辑是写在 OrderProcessor 里的,你只需要改 InventoryService 里的 SQL 查询,或者改一下 OrderProcessor 调用的接口。物理隔离墙越厚,外面的风吹草雨就越伤不到里面的核心。

第十二章:总结(不总结,直接结束)

好了,今天的讲座就到这里。

我们讨论了如何通过定义标准的接口,利用中间件模式,把你的业务逻辑从各个框架的泥潭中拔出来。我们构建了一个 RequestHandlerInterface 作为核心,利用 FrameworkMiddlewareAdapter 作为桥梁。

我们证明了,Laravel、Symfony、Slim、甚至 WordPress,都只是中间件管道里的一个插件。而你的业务代码,是那个永远在管道尽头发光发热的引擎。

记住,不要做代码的奴隶,要做代码的指挥家。让你的框架来适应你,而不是让你去适应框架。

现在,去把你们那个乱七八糟的文件夹结构扔进垃圾桶吧。把核心逻辑提取出来,穿上中间件做成的西装,优雅地跑起来。

谢谢大家。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注