静态变量在hyperf协程中会“串数据”是因为其进程级共享特性与协程复用机制冲突;context通过协程id隔离存储实现数据安全,但需注意手动传递上下文及避免成员变量共享。

为什么静态变量在Hyperf协程里会“串数据”
因为静态变量是进程级的,而Hyperf运行在Swoole协程中——同一个Worker进程会复用处理成百上千个请求,所有协程共享同一份static变量。A协程写入static $user = ['id' => 1],B协程紧接着读,可能拿到的就是这个值,而不是它自己的用户数据。
这不是PHP bug,是协程模型和静态作用域天然冲突。你看到的“用户A订单显示在用户B页面”,根源往往就在这里。
Context::set() 和 Context::get() 是怎么隔离数据的
Context底层用协程ID(Co::id())做键,把数据存在当前协程专属的内存槽位里。每次Context::set('key', $value),实际是往[$cid]['key'] = $value里写;Context::get('key')则只从当前$cid下取,完全不碰其他协程的数据。
关键点:
- 必须在协程内调用,否则
Co::id()为0或无效,上下文失效 - 不传默认值时,
Context::get('missing')返回null,建议显式传默认值避免空指针 - 父子协程默认不继承上下文,需手动
Context::copy()或在子协程中重新set
中间件里存用户信息,别用$this->user,改用Context
单例中间件(默认行为)的实例在整个Worker生命周期内只创建一次,成员变量就是共享的“雷区”。下面这种写法必然出问题:
class UserMiddleware implements MiddlewareInterface
{
protected $user; // ❌ 危险:多个协程共用
public function process($request, $handler)
{
$this->user = $request->getAttribute('user'); // 覆盖上一个协程的值
return $handler->handle($request);
}
}
正确做法是立刻存进Context:
use Hyperf\Context\Context;
class UserMiddleware implements MiddlewareInterface
{
public function process($request, $handler)
{
$user = $request->getAttribute('user');
Context::set('user', $user); // ✅ 协程隔离
return $handler->handle($request);
}
}
后续任意位置(控制器、服务类、定时任务)都可安全获取:
$user = Context::get('user', []); // 第二个参数是默认值,推荐带上
容易被忽略的坑:协程切换后Context没自动传递
当你用Coroutine::create()、go()或异步任务(如AsyncQueue)启动新协程时,父协程的Context不会自动带过去。这是最常被忽视的点——你以为子协程能拿到user,结果是null。
解决方式只有两种:
- 显式复制:
Context::copy($parentCid, $childCid)(需先获取父协程ID) - 更推荐:在子协程开始时,重新从请求或上游来源获取并
set,比如从$request或Context::get('request')里提取
尤其注意AsyncQueue消费者,它运行在独立协程中,必须自己重建上下文,不能依赖父协程遗留数据。











