hyperf中协程上下文数据拼接需安全组合多级或并行协程的上下文片段,避免覆盖污染;应使用context::override实现增量修改,用channel或带cid后缀的context::set聚合并行结果,并显式透传叠加父子上下文。

在Hyperf中拼接协程上下文数据,不是把多个Context::get()结果简单相加或合并数组,而是指在多级协程嵌套或并行协程间,安全、可追溯地组合来自不同协程阶段的上下文片段,避免覆盖、错乱或丢失。这一步若做错,用户ID、请求ID、权限标识等关键字段会相互污染,导致“张三订单显示李四收货地址”这类线上事故。
用 Context::override 安全拼接同一键名的嵌套值
当父子协程都需要更新同一个上下文键(如'user.profile'),且子协程需基于父协程已有值做增量修改时,必须用 override 而非 set。
第一步:父协程初始化基础结构
Context::set('user.profile', ['id' => 1001, 'name' => '张三']);
第二步:子协程执行条件性拼接
Context::override('user.profile', function ($current) {
return array_merge($current ?? [], ['avatar' => 'https://a.jpg', 'level' => 5]);
});
这一步不能用 Context::set('user.profile', [...]),否则会彻底覆盖父协程写入的 id 和 name —— 【override 的闭包参数 $current 是当前协程可见的最新值,不是全局值】,它天然保证了“读-改-写”原子性,避免竞态覆盖。
跨协程并行任务的数据聚合
多个协程并行执行后,需将各自产出的上下文片段合并为完整结果,例如:订单校验协程返回风控标记,库存协程返回可用数量,优惠券协程返回折扣信息。
方法一:用 Channel 收集各协程输出
创建容量为3的有缓冲 channel:
$channel = new Channel(3);
启动三个协程,每个协程处理完后向 channel 推送关联数组:
Coroutine::create(fn() => { $data = ['risk_level' => 'low']; $channel->push($data); });
主协程循环 pop 三次,合并所有结果:
$merged = []; for ($i = 0; $i pop()); }
方法二:用 Context::set 带协程 ID 后缀隔离存储
每个子协程先获取自身 cid:
$cid = Coroutine::getCid();
再以 cid 为后缀存入独立键:
Context::set("order.check.{$cid}", ['stock_ok' => true]);
主协程遍历已知 cid 列表(需提前记录)逐个 get 并合并:
$result = []; foreach ($cids as $cid) { $result = array_merge($result, Context::get("order.check.{$cid}") ?? []); }
【注意:不要在子协程里直接 Context::set('order.check', ...) —— 这会导致最后写入者覆盖全部结果】
父子协程间透传并叠加上下文
父协程设好基础上下文后,子协程既要继承又要补充,此时不能只取不存,也不能盲目覆盖。
父协程设置原始数据:
Context::set('request.trace_id', 'trace-abc123');
Context::set('user.auth', ['role' => 'member']);
子协程手动透传并叠加:
$traceId = Context::get('request.trace_id');
$auth = Context::get('user.auth') ?? [];
Context::set('request.trace_id', $traceId);
Context::set('user.auth', array_merge($auth, ['scope' => ['order:write']]));
这一步必须显式 get → merge → set,因为 Context 不支持自动继承;【若漏掉 get 原始值,子协程 set 就等于清空父协程写入】。











