Hyperf 控制器(Controller)开发指南:路由绑定、请求响应注入与协程安全实践
发布时间:2026/10/8 1:56:14 作者:尧图编辑部 阅读量:1,286
开发指南:路由绑定、请求响应注入与协程安全实践)
后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载Hyperf 的控制器Controller是处理 HTTP 请求的核心入口通过配置文件或注解将路由与控制器方法绑定借助依赖注入容器自动注入Hyperf\HttpServer\Contract\RequestInterface与Hyperf\HttpServer\Contract\ResponseInterface来获取入参、返回数据。本文以官方文档为骨架结合仓库源码src/http-server、src/context深入讲解控制器编写、路由绑定方式以及协程场景下「单例 Controller 协程上下文」的并发安全原理帮助你写出既规范又线程协程安全的 HTTP 业务代码。控制器在 Hyperf 中的定位在 Hyperf 中控制器本身并不负责路由匹配路由与控制器方法的绑定通过以下两种方式之一完成配置文件路由在config/routes.php骨架项目默认位置中通过Hyperf\HttpServer\Router\Router门面类显式注册注解路由直接在控制器类上使用#[Controller]或#[AutoController]注解声明配合#[RequestMapping]等 Mapping 注解细化方法级路由。路由的底层实现与全部细节请参阅 路由 章节。对于请求(Request)与响应(Response)Hyperf 提供了两个契约接口Hyperf\HttpServer\Contract\RequestInterface用于获取入参query、body、路由参数、请求头、Cookie、上传文件等Hyperf\HttpServer\Contract\ResponseInterface用于构建返回数据JSON、XML、HTML、纯文本、重定向、文件下载等。关于两者的完整方法说明可查阅 请求 与 响应 章节。需要说明的是这两个接口扩展自 PSR-7 规范RequestInterface extends ServerRequestInterface见 RequestInterface.php因此 PSR-7 的标准方法在控制器中同样可用。编写你的第一个控制器控制器本质上就是一个普通的 PHP 类无需继承任何抽象基类。在方法参数上声明RequestInterface与ResponseInterface依赖注入容器会自动完成注入?php declare(strict_types1); namespace App\Controller; use Hyperf\HttpServer\Contract\RequestInterface; use Hyperf\HttpServer\Contract\ResponseInterface; class IndexController { // 在参数上通过定义 RequestInterface 和 ResponseInterface 来获取相关对象 // 对象会被依赖注入容器自动注入 public function index(RequestInterface $request, ResponseInterface $response) { $target $request-input(target, World); return Hello . $target; } }我们假设该Controller已经通过配置文件的形式定义了路由为/详见下文「路由绑定」一节当然您也可以使用注解路由。启动服务默认 HTTP Server 监听0.0.0.0:9501后通过cURL调用该地址即可看到返回的内容$ curl http://127.0.0.1:9501/?targetHyperf Hello Hyperf.注意这里有两个细节值得说明方法返回字符串即可作为响应体index()直接返回Hello . $targetHyperf 的核心中间件会将字符串返回值转换为响应。从源码看CoreMiddleware中存在transferToResponse()方法专门负责将控制器返回值字符串、数组、PSR-7 响应对象等转换为真正的ResponseInterface见 CoreMiddleware.php对应测试见 CoreMiddlewareTest.php。依赖注入是自动完成的容器会根据方法参数的类型声明把当前协程上下文中的请求/响应对象注入进来无需手动获取。路由绑定配置文件与注解两种方式通过配置文件定义路由骨架项目默认在config/routes.php中完成所有路由定义。最简单的方式是使用Hyperf\HttpServer\Router\Router门面类?php use Hyperf\HttpServer\Router\Router; // 闭包路由 Router::get(/hello-hyperf, function () { return Hello Hyperf.; }); // 标准路由将 /hello-hyperf 绑定到 App\Controller\IndexController 的 hello 方法 // 下面三种写法效果相同 Router::get(/hello-hyperf, App\Controller\IndexController::hello); Router::get(/hello-hyperf, App\Controller\IndexControllerhello); Router::get(/hello-hyperf, [App\Controller\IndexController::class, hello]);路由门面还提供了与 HTTP 方法同名的注册方法以及通用方法use Hyperf\HttpServer\Router\Router; // 注册与方法名一致的 HTTP METHOD 的路由 Router::get($uri, $callback); Router::post($uri, $callback); Router::put($uri, $callback); Router::patch($uri, $callback); Router::delete($uri, $callback); Router::head($uri, $callback); // 注册任意 HTTP METHOD 的路由可同时响应多种请求方式 Router::addRoute([GET, POST, PUT, DELETE], $uri, $callback);也可以使用路由组统一管理前缀// 实际路由为 /user/index、/user/store、/user/update、/user/delete Router::addGroup(/user/, function () { Router::get(index, App\Controller\UserControllerindex); Router::post(store, App\Controller\UserControllerstore); Router::get(update, App\Controller\UserControllerupdate); Router::post(delete, App\Controller\UserControllerdelete); });从源码结构看Router是一个静态门面__callStatic()会把调用代理给DispatcherFactory生成的对应 HTTP Server 路由器实例见 Router.php底层路由匹配由nikic/fast-route提供支持。通过注解定义路由Hyperf 提供非常便利的注解路由功能可直接在任意类上通过#[Controller]或#[AutoController]注解完成路由定义。以下注解类均位于Hyperf\HttpServer\Annotation\命名空间。#[AutoController]简单场景的自动路由#[AutoController]适用于绝大多数简单访问场景使用后 Hyperf 会自动解析所在类的所有public方法并为每个方法生成GET与POST两种请求方式的路由?php declare(strict_types1); namespace App\Controller; use Hyperf\HttpServer\Contract\RequestInterface; use Hyperf\HttpServer\Annotation\AutoController; #[AutoController] class UserController { // Hyperf 会自动为此方法生成一个 /user/index 的路由允许通过 GET 或 POST 方式请求 public function index(RequestInterface $request) { // 从请求中获得 id 参数 $id $request-input(id, 1); return (string)$id; } }#[Controller]精细的路由控制#[Controller]用于表明当前类是一个控制器需配合#[RequestMapping]对请求方法与路径做更细致的定义同时框架还提供了#[GetMapping]、#[PostMapping]、#[PutMapping]、#[PatchMapping]、#[DeleteMapping]五种快捷 Mapping 注解?php declare(strict_types1); namespace App\Controller; use Hyperf\HttpServer\Contract\RequestInterface; use Hyperf\HttpServer\Annotation\Controller; use Hyperf\HttpServer\Annotation\RequestMapping; #[Controller] class UserController { // Hyperf 会自动为此方法生成一个 /user/index 的路由允许通过 GET 或 POST 方式请求 #[RequestMapping(path: index, methods: get,post)] public function index(RequestInterface $request) { // 从请求中获得 id 参数 $id $request-input(id, 1); return (string)$id; } }注解参数prefix与server#[Controller]与#[AutoController]都提供prefix和server两个参数源码见 Controller.php 与 AutoController.phpprefix表示该控制器下所有方法路由的前缀。默认取控制器类命名空间中\Controller\之后的部分并按蛇形命名法SnakeCase作为前缀。例如App\Controller\Demo\UserController的 prefix 默认为demo/user若类内某方法 path 为index则最终路由为/demo/user/index。注意prefix并非一直有效——当方法内的 path 以/开头时表示路径从 URI 头部开始定义此时会忽略prefix的值。server表示该路由定义在哪个 HTTP Server 之上。由于 Hyperf 支持同时启动多个 HTTP Server可通过该参数区分路由归属默认为http。下表总结了不同命名与注解组合下最终生成的路由控制器注解访问路由App\Controller\MyDataControllerAutoController()/my_data/indexApp\Controller\MydataControllerAutoController()/mydata/indexApp\Controller\MyDataControllerAutoController(prefix/data)/data/indexApp\Controller\Demo\MydataControllerAutoController()/demo/mydata/indexApp\Controller\Demo\MyDataControllerAutoController(prefix/data)/data/index控制器注解访问路由App\Controller\MyDataControllerController()RequestMapping(path: index, methods: get,post)/my_data/indexApp\Controller\Demo\MyDataControllerController()RequestMapping(path: index, methods: get,post)/demo/my_data/indexApp\Controller\Demo\MyDataControllerController(prefix/data)RequestMapping(path: index, methods: get,post)/data/indexApp\Controller\MyDataControllerController()RequestMapping(path: /index, methods: get,post)/index路由参数与获取路由参数支持必填、可选与正则校验等丰富定义方式详见 路由 章节例如Router::get(/user/{id}, App\Controller\UserController::info);在控制器中路由参数既可以通过方法同名参数自动注入也可以通过$request-route(id)获取route()方法从当前请求属性中的Dispatched对象读取匹配参数见 Request.phppublic function index(RequestInterface $request) { // 存在则返回不存在则返回默认值 null $id $request-route(id); // 存在则返回不存在则返回默认值 0 $id $request-route(id, 0); }请求与响应的获取与常用操作RequestInterface读取入参控制器方法中通过类型声明注入Hyperf\HttpServer\Contract\RequestInterface即可调用其丰富的方法读取请求数据。常用方法如下完整定义见 RequestInterface.php方法作用示例input(string $key, mixed $default null)获取入参合并 query、解析后的 body 与 JSON body$request-input(target, World)query(?string $key, mixed $default null)仅获取 query 参数$key为null时返回全部$request-query(page, 1)post(?string $key, mixed $default null)仅获取解析后的 body 参数$request-post(name)inputs(array $keys, ?array $default null)批量获取多个入参$request-inputs([id, name])all(): array获取全部入参query body$request-all()has(string\|array $keys): bool判断入参是否存在$request-has(id)header(string $key, ?string $default null)获取请求头$request-header(X-Token)route(string $key, mixed $default null)获取路由参数$request-route(id)cookie(string $key, mixed $default null)获取 Cookie$request-cookie(session_id)server(string $key, mixed $default null)获取服务器变量$request-server(request_uri)file(string $key, mixed $default null)获取上传文件UploadedFile$request-file(avatar)isMethod(string $method)判断请求方法$request-isMethod(POST)其中input()的实现值得留意它通过getInputData()将 query 参数与解析后的 body 合并取并集且解析结果会被缓存到协程上下文中key 为http.request.parsedData避免同一次请求内重复解析见 Request.php 与 Request.php。ResponseInterface构建返回数据Hyperf\HttpServer\Contract\ResponseInterface提供了多种格式化响应方法实现见 Response.phppublic function json($data): PsrResponseInterface // JSONContent-Type: application/json public function xml($data, string $root root, string $charset utf-8): PsrResponseInterface public function html(string $html, string $charset utf-8): PsrResponseInterface public function raw($data, string $charset utf-8): PsrResponseInterface // 纯文本 public function redirect(string $toUrl, int $status 302, string $schema http): PsrResponseInterface public function download(string $file, string $name ): PsrResponseInterface // 文件下载典型用法public function index(RequestInterface $request, ResponseInterface $response) { // 返回 JSON return $response-json([code 0, data [id 1]]); }download()方法还会根据请求头If-Match/If-None-Match做 ETag 条件判断命中时直接返回304见 Response.phpredirect()在目标地址不含协议头时会基于当前请求的 Host 自动拼装完整 URL见 Response.php。避免协程间的数据混淆这是 Hyperf 控制器开发中最重要的注意事项也是与 PHP-FPM 传统框架差异最大的地方。为什么不能在类属性中保存请求数据在传统的 PHP-FPM 框架中通常会提供一个AbstractController或其他命名的Controller 抽象父类定义的Controller需要继承它来获取请求数据或进行返回操作。在 Hyperf 中不能这样做原因在于Hyperf 内绝大部分对象包括Controller都以**单例Singleton**形式存在这是为了更好的复用对象在协程模型下与单个请求相关的数据必须存储到**协程上下文Context**中。因此编写代码时请务必注意不要将单个请求相关的数据储存在类属性内包括非静态属性。否则由于控制器是单例多个协程并发处理请求时会共享同一份属性数据后写入的数据可能覆盖先写入的数据造成请求间的数据混淆。协程安全的实现原理代理对象 协程上下文那么问题来了通过注入获取的RequestInterface对象本身不也是单例吗它是如何做到协程安全的呢以RequestInterface为例实际注入的对象是Hyperf\HttpServer\Request它内部获取 PSR-7 请求对象时总是从协程上下文Context中获取。也就是说这个类实际上只是一个代理类所有真正被调用的数据都来自协程上下文。从源码可以确认这条完整的调用链请求写入协程上下文核心中间件CoreMiddleware在分发请求时执行RequestContext::set($request)将 PSR-7 请求对象写入当前协程的上下文见 CoreMiddleware.php。协程上下文按协程隔离Hyperf\Context\Context的set()/get()通过Coroutine::id()判断当前协程数据实际存放在该协程独立的上下文中见 Context.php。代理类从上下文读取Hyperf\HttpServer\Request::getRequest()直接返回RequestContext::get()见 Request.phpRequestContext::get()本质上是Context::get(ServerRequestInterface::class, ...)见 RequestContext.php。PSR-7 标准方法全部代理转发Request类中的getMethod()、getUri()、getQueryParams()、getParsedBody()等大量方法统一通过call()代理到上下文中的真实 PSR-7 请求对象上执行见 Request.php。测试代码也印证了这一机制在 CoreMiddlewareTest.php 中测试通过Context::set(ServerRequestInterface::class, $request)手动把请求放入协程上下文再让路由闭包通过Context::get(ServerRequestInterface::class)取出并使用。Response同理其构造器接收一个可选的 PSR-7 响应对象方法调用json()、html()等最终都会落到从上下文中获取的真实响应对象上见 Response.php。正确的实践方式基于上述机制编写控制器时应遵循以下实践不要在控制器类中定义用于存储请求数据的属性包括非静态属性需要传递请求级数据时优先使用协程上下文Hyperf\Context\Context或在方法参数间显式传递善用依赖注入获取RequestInterface/ResponseInterface让代理对象从当前协程上下文中取数天然具备协程隔离性若确实需要把数据暂存在类属性中必须清楚地认识到单例 多协程并发下的覆盖风险并对生命周期做显式管理。小结本文围绕 Hyperf 控制器的完整开发链路展开从路由绑定配置文件Router门面 /#[AutoController]、#[Controller]注解、依赖注入的请求响应对象到协程上下文机制下的并发安全原理。核心要点可归纳为控制器是普通类无需继承抽象父类路由通过配置文件或注解绑定通过RequestInterface/ResponseInterface的类型声明即可获得自动注入的请求与响应对象控制器以单例形式复用请求相关数据必须依赖协程上下文存取——注入的Request本质上是一个从协程上下文读取数据的代理类这正是协程安全的根本保证。进一步深入可继续阅读仓库中的 路由、请求、响应 与 注解 章节或在src/http-server/src下直接查看Request.php、Response.php、CoreMiddleware.php与src/context/src下的Context.php、RequestContext.php源码理解底层实现细节。赞分享后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载相关推荐Hyperf 控制器Controller开发指南路由绑定、请求响应注入与协程安全实践Hyperf 控制器Controller开发指南路由绑定、请求响应注入与协程安全实践 导读 本文以 Hyperf 官方文档中的 Controller 章节后端微服务Midway 路由与控制器Controller完全指南从装饰器到请求响应全流程Midway 路由与控制器Controller完全指南从装饰器到请求响应全流程 Midway 是一个面向 Node.js 前后端全栈场景的 Serverl后端微服务云原生Egg.js HTTP Controller 装饰器实战指南声明式路由、参数注入与响应定制Egg.js HTTP Controller 装饰器实战指南声明式路由、参数注入与响应定制 导读 HTTP Controller 是 Egg.js基于 Ty后端Web框架上一篇终极炉石传说优化插件55个功能全面升级你的游戏体验下一篇炉石传说终极优化指南HsMod插件55项功能全面解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考