当你在浏览器输入 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 后台收尾。这七个阶段构成了请求的完整生命周期,每一步都可能成为性能瓶颈或扩展点。看懂了这条链路,调试任何框架问题时你都知道该从哪入手,扩展功能时也知道该挂在哪个环节——这正是"源码讲解"系列想带给你的全局视野。