TUTORIAL

框架请求生命周期

框架请求生命周期

框架请求生命周期

当你在浏览器输入 ldpk.cn/article/123 回车,到页面显示出来,这中间发生了什么?对很多开发者来说是黑盒——"反正框架处理了"。但真正的高手必须看清这个黑盒:请求从哪进、经过哪些环节、最后怎么变成响应。理解请求生命周期,是排查疑难问题、做性能优化、扩展框架功能的基础。本篇以一个典型建站框架为例,拆解请求从入口到响应的完整旅程。

一、入口文件与引导阶段

所有请求都从入口文件 index.php 开始(伪静态把所有动态请求都转给它)。入口文件非常精简,只做三件事:定义常量、加载自动加载、启动应用。真正的初始化工作在"引导阶段"完成——加载配置、注册服务、绑定容器。现代框架用"服务容器"(IoC 容器)管理所有组件,需要时才实例化,降低启动开销。

// index.php 入口文件
make(Kernel::class);

// 把请求交给内核处理,得到响应
$request = Request::capture();
$response = $kernel->handle($request);
$response->send();

// 收尾:执行后续任务(如写日志、释放资源)
$kernel->terminate($request, $response);

// bootstrap/app.php 引导阶段
$app = new Application(ROOT_PATH);
$app->singleton('config', function () {
  return new ConfigLoader();  // 加载 config/*.php 配置
});
$app->singleton('db', function ($c) {
  return new Database($c['config']->get('database'));  // 懒加载,用到才连
});
$app->singleton('router', function () {
  return new Router();
});
// 注册服务提供者(注册事件、加载路由文件等)
foreach ($app['config']->get('app.providers') as $provider) {
  (new $provider($app))->register();
}
return $app;

引导阶段最容易出"启动慢"的问题。尧图曾遇到一个站点每个请求都要 300ms 启动,排查发现是某个服务提供者在注册时就连接了外部 API 做初始化。改成懒加载后启动降到 20ms。原则是:注册阶段只做"声明",不做"执行"——告诉容器"怎么造这个组件",但别真的去造。

二、路由分发与中间件

应用启动后,内核把请求交给路由器。路由器根据 URL 和 HTTP 方法找到匹配的路由规则,取出对应的控制器和方法。但请求不会直接到控制器——它要先穿过一系列"中间件"。中间件像安检门,请求依次通过每道门,每道门可以做鉴权、日志、CORS 等处理,任何一道门拒绝(返回响应),请求就到此为止。

// 内核处理请求的核心流程
class Kernel {
  handle(request) {
    // 1. 路由匹配:找到处理这个 URL 的控制器和方法
    const route = this.router.match(request.method, request.path);
    if (!route) return new Response(404, 'Not Found');

    // 2. 组装中间件链(全局中间件 + 路由组中间件 + 路由中间件)
    const middlewares = [
      ...this.globalMiddlewares,        // 全局:如维护模式检查
      ...route.middlewares              // 路由:如 auth、throttle
    ];

    // 3. 把"控制器执行"包成最内层的处理函数
    const dispatch = async (req) => {
      const controller = this.app.make(route.controller);
      const result = await controller[route.action](req);
      return this.toResponse(result);  // 把控制器返回值转成Response对象
    };

    // 4. 中间件层层包裹(洋葱模型)
    //    请求从外向内穿:before 逻辑
    //    响应从内向外穿:after 逻辑
    const handler = middlewares.reduceRight(
      (next, middleware) => {
        return async (req) => {
          const instance = this.app.make(middleware);
          return instance.handle(req, next);  // next 调用才进入下一层
        };
      },
      dispatch  // 最内层是控制器执行
    );

    return handler(request);  // 启动洋葱模型执行
  }
}

// 一个中间件示例:登录鉴权
class AuthMiddleware {
  async handle(request, next) {
    // before:检查登录状态
    if (!request.session.get('user_id')) {
      return new Response(401, '请先登录');  // 拒绝,不调 next
    }
    // 放行:调 next 进入下一层
    const response = await next(request);
    // after:可修改响应(如加响应头)
    response.setHeader('X-Authed', '1');
    return response;
  }
}

洋葱模型是中间件的精髓:next 之前的代码在请求"进入"时执行,next 之后的代码在响应"返回"时执行。这让中间件既能做请求前置处理(鉴权、参数过滤),也能做响应后置处理(加响应头、记录耗时)。尧图在性能监控中间件里这样用:进入时记开始时间,next 后记结束时间,算出请求耗时写入日志。

三、控制器执行与响应输出

穿过所有中间件后,请求到达控制器。控制器是业务逻辑的入口——它接收请求参数、调用模型/服务层处理业务、把结果封装成响应返回。控制器应该保持"瘦"——只做参数接收和响应组装,业务逻辑下沉到 Service 层,数据操作下沉到 Model 层。

// 控制器示例:文章详情页
class ArticleController {
  async show(request) {
    // 1. 接收并校验参数
    const id = request.param('id');
    if (!/^\d+$/.test(id)) {
      return new Response(404, '文章不存在');
    }

    // 2. 调用 Service 层处理业务(控制器不直接操作数据库)
    const article = await ArticleService.getDetail(id);
    if (!article) return new Response(404, '文章不存在');

    // 3. 调用视图渲染(或返回 JSON)
    const html = view.render('article/show', {
      article,
      related: await ArticleService.related(article.category_id, 5)
    });
    return new Response(200, html);
  }
}

// 响应发送阶段
class Response {
  constructor(status, content) {
    this.status = status;
    this.content = content;
    this.headers = { 'Content-Type': 'text/html; charset=utf-8' };
  }

  send() {
    // 1. 发送状态码
    http_response_code(this.status);
    // 2. 发送响应头
    for (const [k, v] of Object.entries(this.headers)) {
      header(`${k}: ${v}`);
    }
    // 3. 发送响应体
    echo this.content;
  }
}

// terminate 阶段:响应已发给用户,后台收尾
$kernel->terminate($request, $response);
// 这里执行:写访问日志、发送统计埋点、清理临时文件
// 不影响用户感知的响应速度

terminate 阶段是个容易被忽视的优化点——响应已经发给用户了,但 PHP 进程还没结束,这段时间可以干"不影响用户体验但必须做"的事,比如写访问日志、发统计埋点、推送消息。把这类任务挪到 terminate,能让用户感知的响应时间显著下降。尧图把"文章访问数 +1"从控制器挪到 terminate 后,详情页 TTFB(首字节时间)从 120ms 降到 80ms。

整条链路串起来:入口捕获请求 → 引导加载配置 → 路由匹配分发 → 中间件洋葱过滤 → 控制器执行业务 → 响应发送输出 → terminate 后台收尾。这七个阶段构成了请求的完整生命周期,每一步都可能成为性能瓶颈或扩展点。看懂了这条链路,调试任何框架问题时你都知道该从哪入手,扩展功能时也知道该挂在哪个环节——这正是"源码讲解"系列想带给你的全局视野。

返回教程列表