Symfony 7.0 中灰度发布需基于特性开关构建应用层控制体系:定义 scope 类型化配置、实现 FeatureManager 按 user/header/percent 分支判断、通过注解驱动中间件拦截并动态更新状态(CLI 或 FeatureProbe SDK)。

在 Symfony 7.0 中实现灰度发布,必须绕过框架原生不支持灰度的限制,转而构建一套基于特性开关(Feature Toggle)的轻量级控制体系——它不依赖服务网格或网关染色,而是直接在应用层拦截请求、读取动态规则、决定功能启用状态。
定义特性开关配置结构
在 config/packages/feature_toggle.yaml 中声明开关元数据,不写死布尔值,只定义开关名、默认行为和作用域类型:
```yaml
feature_toggle:
enable_new_checkout: { default: false, scope: 'user' }
enable_ai_suggestions: { default: false, scope: 'header' }
enable_bulk_import: { default: true, scope: 'percent' }
```
这一步是后续所有逻辑的契约基础。若此处 scope 类型与实际判断逻辑不匹配,中间件将无法提取有效上下文。
实现 FeatureManager 服务
创建 src/Service/FeatureManager.php,封装核心判断逻辑:
构造函数注入 CacheInterface 和 RequestStack;
提供 shouldEnable(string $featureName): bool 方法;
内部按 scope 类型分支处理:user → 取 $this->requestStack->getCurrentRequest()?->getUser()?->getId();header → 取 $request->headers->get('X-Feature-Group');percent → 用 mt_rand(1, 100) 模拟分流。
【缓存键必须包含 scope 值,否则同一开关对所有用户返回相同结果】
注册特性开关中间件
新建 src/Middleware/FeatureGateMiddleware.php,实现 MiddlewareInterface:
第一步:从 $request->attributes 提取当前控制器方法标注的 @Feature("enable_new_checkout") 注解;
第二步:调用 FeatureManager::shouldEnable() 获取布尔结果;
第三步:若返回 false 且该功能为强制灰度项,则抛出 NotFoundHttpException;否则注入 $request->attributes->set('feature_enabled', true) 供控制器读取。
在 config/services.yaml 中绑定中间件并设置优先级高于安全认证但低于会话启动:
```yaml
App\Middleware\FeatureGateMiddleware:
tags: [{ name: 'kernel.middleware', priority: 10 }]
```
用注解驱动控制器行为
在控制器方法上添加自定义注解:
```php
#[Feature('enable_ai_suggestions')]
public function search(Request $request): Response { ... }
```
注解解析由中间件完成,控制器内无需重复判断。若注解存在但开关关闭,中间件已提前终止请求,控制器根本不会执行。
这避免了在每个 action 里写 if (! $this->featureManager->shouldEnable(...)) { throw new AccessDeniedException(); } 的硬编码陷阱。
动态更新开关状态
方法一:通过命令行临时开启
运行 php bin/console feature:enable enable_bulk_import --env=prod,该命令将开关状态写入 Redis 缓存,键名为 feature:enable_bulk_import:global,过期时间设为 300 秒。
方法二:接入 FeatureProbe SDK
在 composer.json 中添加 "featureprobe/server-sdk-php": "^2.4";
初始化客户端时传入 SDK key 和服务地址;
替换 FeatureManager::shouldEnable() 内部实现为 $this->fpClient->boolVariation($featureName, $context, false);
【务必确保 $context 包含 user_id、ip、headers 等字段,否则 FeatureProbe 无法做精准分流】


















