kode/context

A lightweight, high-performance context management library for PHP coroutine/fiber environments

1.1.0 2025-09-19 03:13 UTC

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 —— 让每一次协程调用都清晰可控,告别上下文污染!