hyperf协程上下文是解决进程级变量污染的唯一可靠路径,因单例对象属性和静态变量在协程间共享,必须用context::set()/get()隔离数据,且子协程默认不继承父协程上下文。

Hyperf 协程上下文不是“可选的工具”,而是解决进程级变量污染的唯一可靠路径——不用 Context::set() 和 Context::get(),就必然出现 A 请求的 $this->user_id 被 B 请求读到。
为什么 $this->xxx 在协程里会串数据
Hyperf 默认把 Service、Middleware、Controller 都注册为单例,整个 Worker 进程只初始化一次实例。协程切换时,PHP 不重置对象属性,$this->cache、$this->errors、$this->requestId 全部留在内存里,下一个协程直接复用。
- 静态变量
static $counter++更危险:它跨所有协程、所有请求共享 - 全局数组(如
$_SESSION)被彻底禁用,框架不提供,用了就错 - 中间件里写
$this->authData = $user,等于把用户信息挂到公共黑板上
Context::set() 必须在协程启动后、数据使用前调用
常见错误是:在非协程环境(如 CLI 命令、单元测试)里提前 Context::set(),或在 go() 之后才写 —— 此时上下文已切换,写入无效。
- 中间件中设置用户信息,必须在
handle()方法内、$next($request)之前:Context::set('user_id', $user->id) - 取值时务必带默认值:
Context::get('user_id', null),否则未 set 就 get 会返回null或触发警告 - 不要存大对象(如完整订单模型),只放轻量元数据:
request_id、trace_id、user_id
子协程拿不到父协程 Context 是设计使然,不是 bug
Swoole 为每个协程分配独立存储空间,Context::set() 写入的数据绑定在当前协程 ID 上。子协程 ID 不同,查不到父协程的数据 —— 这是防止高并发下数据污染的关键机制。
- 手动复制上下文最常用:
go(function () use ($context) { Context::copy($context); }, Context::all()); - 需多层子协程共享时,改用根协程 ID 存储:
Context::set(getRootCid(), 'user_id', 123),所有子协程统一用getRootCid()读取 -
Coroutine::create()、AsyncTask::execute()、Co::defer()都不自动继承上下文,必须显式传参或 set
真正容易被忽略的是:Context 不是“替代方案”,而是协程模型下的基础设施。你在 Controller 里写 $request->getAttribute('user_id') 安全,是因为框架已把它注入 Request;但一旦跨服务、跨异步任务、跨 RPC,Request 就失效了——这时候没用 Context,就等于裸奔。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











