kode / context
A lightweight, high-performance context management library for PHP coroutine/fiber environments
Requires
- php: ^8.1
Requires (Dev)
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^9.0
This package is auto-updated.
Last update: 2025-09-19 04:03:07 UTC
README
为多线程、多进程、协程(Swoole/Swow/Fiber)环境提供安全的请求上下文传递机制
📌 概述
在现代 PHP 高并发编程中,尤其是在使用 协程(Coroutine) 或 纤程(Fiber) 的场景下,传统的全局变量、静态属性或单例模式极易导致上下文污染和数据错乱。例如,在一个 HTTP 请求中存储用户信息、Trace ID、请求对象等,若直接使用 static
变量或全局容器,多个并发协程会共享同一份内存,造成严重安全隐患。命名简洁,不与php原生冲突。写一个更健壮的架构和代码。
kode/context
是一个轻量级、高性能、跨运行时的上下文管理库,旨在解决:
- ✅ Fiber 中
static
变量被共享导致的数据污染 - ✅ Swoole/Swow 协程间上下文隔离问题
- ✅ 支持透明传递请求上下文(如:
user
,request
,trace_id
) - ✅ 提供与 Go
context.Context
类似的语义模型 - ✅ 兼容原生 PHP、Fiber(PHP 8.3+)、Swoole、Swow 等多种运行时环境
- ✅ 支持 PHP 8.1+ 的
Fiber
运行时 - ✅ 协变/逆变,反射等更安全的方式实现。
🎯 为什么需要 kode/context
?
场景 | 问题 | 解决方案 |
---|---|---|
原生 PHP + 多进程 | 进程隔离,无需担心共享状态 | ✅ 安全 |
原生 PHP + 多线程(ZTS) | 线程共享内存,static 被所有线程共享 |
❌ 存在风险 |
Swoole 协程 | 协程共享线程内存,static 被复用 |
❌ 极易污染 |
Swow 协程 | 同上,绿色线程模型 | ❌ 存在上下文混淆 |
PHP 8.3+ Fiber | Fiber 共享调用栈中的 static 变量 |
❌ 数据交叉污染 |
👉 结论:只要存在“并发执行单元共享主线程内存”的情况,就必须使用上下文隔离机制!
🔥 特别提醒:这是解决 Facade 模式、Service Locator、静态容器等“全局状态”污染的关键!
🧩 核心功能
// 设置上下文值 Context::set('user', $user); // 获取上下文值 $request = Context::get('request'); // 判断是否存在 if (Context::has('trace_id')) { ... } // 删除键 Context::delete('tmp_data'); // 复制当前上下文快照 $ctx = Context::copy(); // 在新上下文中运行闭包(不影响父上下文) Context::run(fn() => { Context::set('temp', 'value'); // ... }); // 自动恢复原始上下文 // 清空当前上下文 Context::clear();
⚙️ 实现原理(按运行时自动适配)
运行时环境 | 上下文存储机制 | 说明 |
---|---|---|
PHP Fiber (8.3+) | \Fiber::getLocal() |
使用 Fiber 内建本地存储,完美隔离 |
Swoole | Co::getuid() + Coroutine::getContext() |
基于协程 ID 绑定上下文对象 |
Swow | Swow\Coroutine::getLocal() |
使用 Swow 提供的本地存储 API |
普通同步环境 | thread_local 模拟(基于数组栈) |
单线程安全,兼容 CLI/HTTP |
✅ 所有实现均保证:每个并发执行单元拥有独立的上下文视图
🧪 快速开始
1. 安装
composer require kode/context
2. 基本用法
use Kode\Context\Context; // 设置一些上下文数据 Context::set('user_id', 123); Context::set('trace_id', uniqid('trace_')); // 在任意深度获取 function getCurrentUser() { return UserService::find(Context::get('user_id')); } // 输出 trace_id echo Context::get('trace_id'); // e.g., trace_abc123
3. 使用 Context::run()
创建隔离作用域
Context::set('role', 'admin'); Context::run(function () { Context::set('role', 'guest'); // 不影响外部 echo Context::get('role'); // "guest" }); echo Context::get('role'); // 仍然是 "admin"
4. 结合中间件使用(如 Swoole HTTP Server)
$http->on('request', function ($req, $resp) { Context::set('request', $req); Context::set('response', $resp); Context::set('trace_id', generateTraceId()); try { $handler->handle(); // 在业务逻辑中可随时通过 Context::get() 获取 } catch (\Throwable $e) { Log::error($e->getMessage(), ['trace_id' => Context::get('trace_id')]); $resp->end('Server Error'); } });
🔄 API 文档
Context::set(string $key, mixed $value): void
设置当前上下文中的值。
Context::get(string $key, mixed $default = null): mixed
获取指定键的值,不存在则返回默认值。
Context::has(string $key): bool
判断键是否存在。
Context::delete(string $key): void
删除指定键。
Context::clear(): void
清空当前上下文所有数据。
Context::copy(): array
复制当前上下文为数组快照(用于调试或传递)。
Context::run(callable $callable): mixed
在新的上下文作用域中执行 $callable
,结束后自动回滚到之前的状态。
💡 类似于事务式的上下文操作,避免副作用泄漏。
Context::keys(): array
获取当前上下文中的所有键名。
Context::count(): int
获取当前上下文中的键值对数量。
Context::all(): array
获取当前上下文中的所有数据。
Context::merge(array $data, bool $overwrite = true): void
将数组合并到当前上下文中。
🧱 设计思想参考
-
Go 的
context.Context
提供了WithValue
,WithCancel
,WithTimeout
等组合能力,本包聚焦于最核心的value
传递。 -
Swoole Coroutine\Context
借鉴其基于协程 ID 的上下文映射机制,确保隔离性。 -
Hyperf\Context
对标其静态代理接口设计,提供更简洁的 API。
🧰 底层实现示例(简化版)
class Context { private static ?array $local = null; public static function set(string $key, $value): void { self::initStorage(); self::$local[$key] = $value; } public static function get(string $key, $default = null) { self::initStorage(); return self::$local[$key] ?? $default; } private static function initStorage(): void { if (self::$local !== null) { return; } if (class_exists(\Fiber::class)) { // PHP 8.3 Fiber $fiber = \Fiber::getCurrent(); self::$local =& $fiber->getLocal()['context'] ?? []; } elseif (extension_loaded('swoole')) { // Swoole $cid = \Swoole\Coroutine::getUid(); if ($cid === -1) { self::$local = []; } else { $ctx = \Swoole\Coroutine::getContext(); self::$local =& $ctx['context'] ?? []; } } elseif (extension_loaded('swow')) { // Swow $co = \Swow\Coroutine::getCurrent(); self::$local =& $co->getLocal()['context'] ?? []; } else { // Sync mode self::$local = &$_GLOBALS['__context'] ?? []; } } }
⚠️ 实际实现需考虑性能优化(如弱引用、GC 回收等)
✅ 适用场景
- 微服务架构中的链路追踪(Trace ID 透传)
- 用户身份认证上下文(User / Token)
- 日志上下文注入(Structured Logging)
- ORM 连接上下文(如 Tenant ID)
- AOP 拦截器中共享临时数据
- 替代 Facade 模式中的全局状态
🚫 注意事项
- 不建议存放大量数据(影响性能)
- 不支持跨协程/纤程通信(仅传递快照)
- 不应在上下文中保存资源句柄(如文件描述符、数据库连接)
- Fiber 下注意闭包绑定问题(
$this
上下文可能不同)
📦 与其他组件集成建议
组件 | 集成方式 |
---|---|
Hyperf | 替代 Hyperf\Context\Context ,作为底层依赖 |
Laravel Octane | 在 onRequest 回调中初始化 Context |
EasySwoole | 在主服务启动时注册 Context 初始化 |
Monolog | 添加 ProcessContextProcessor 注入 trace_id |
🧪 性能基准测试
在 Windows 11 (AMD Ryzen 7 5800H, 32GB RAM) 上对 kode/context
进行了性能测试,迭代次数 100,000 次:
方法 | 执行时间 | 每秒操作数 |
---|---|---|
Context::set() |
8.38ms | 11,930,549 |
Context::get() |
8.81ms | 11,346,997 |
Context::has() |
8.11ms | 12,335,099 |
Context::delete() |
14.74ms | 6,782,509 |
Context::clear() |
22.14ms | 4,516,074 |
Context::copy() |
8.44ms | 11,855,349 |
Context::run() |
36.10ms | 2,770,016 |
Context::keys() |
9.46ms | 10,569,523 |
Context::count() |
9.47ms | 10,560,741 |
Context::all() |
7.98ms | 12,533,030 |
Context::merge() |
16.60ms | 6,022,664 |
这些结果表明 kode/context
在各种操作上都具有出色的性能表现,适合在高并发环境中使用。
🤝 贡献与反馈
欢迎提交 Issue 或 Pull Request!
GitHub: https://github.com/kode-php/context
📜 许可证
Apache License 2.0
🌟
kode/context
—— 让每一次协程调用都清晰可控,告别上下文污染!