本文目录导读:

在 PHP 中实现"特性开关"(Feature Flags/Toggles)有几种常见方案,我来详细介绍:
配置文件方案(最简单)
使用 PHP 常量
// config.php
define('FEATURE_NEW_UI', true);
define('FEATURE_PAYMENT', false);
define('FEATURE_EXPORT', true);
// 使用
if (FEATURE_NEW_UI) {
// 新UI代码
} else {
// 旧UI代码
}
使用配置文件数组
// config.php
return [
'features' => [
'new_ui' => true,
'payment' => false,
'export' => ['status' => true, 'version' => '2.0']
]
];
// 使用
$config = require 'config.php';
if ($config['features']['new_ui']) {
// 新功能代码
}
环境变量方案(适合 DevOps)
// .env 文件
FEATURE_NEW_UI=true
FEATURE_PAYMENT=false
// 使用
if (getenv('FEATURE_NEW_UI')) {
// 新功能
}
// 或者用 $_ENV
if ($_ENV['FEATURE_NEW_UI'] ?? false) {
// 新功能
}
数据库方案(动态控制)
class FeatureFlag {
private $db;
private $cache = [];
public function __construct(PDO $db) {
$this->db = $db;
}
public function isEnabled(string $feature): bool {
// 使用缓存避免重复查询
if (!isset($this->cache[$feature])) {
$stmt = $this->db->prepare(
"SELECT enabled FROM feature_flags WHERE name = ?"
);
$stmt->execute([$feature]);
$this->cache[$feature] = (bool)$stmt->fetchColumn();
}
return $this->cache[$feature];
}
public function enable(string $feature): void {
$stmt = $this->db->prepare(
"INSERT INTO feature_flags (name, enabled) VALUES (?, 1)
ON DUPLICATE KEY UPDATE enabled = 1"
);
$stmt->execute([$feature]);
unset($this->cache[$feature]);
}
}
// 使用
$featureFlag = new FeatureFlag($db);
if ($featureFlag->isEnabled('new_ui')) {
// 新功能
}
第三方库方案(功能强大)
使用 Laravel 的配置
// config/features.php
return [
'stats' => env('FEATURE_STATS', true),
'chat' => env('FEATURE_CHAT', false),
];
// 使用
if (config('features.stats')) {
// 统计功能
}
使用 Symfony 的 Feature Flag Bundle
// services.yaml
services:
feature_flags:
class: App\Service\FeatureFlags
arguments:
- 'redis://localhost'
// 使用
if ($this->featureFlags->isEnabled('new_checkout')) {
// 新结算流程
}
实时动态方案(Redis 缓存)
class DynamicFeatureFlag {
private $redis;
public function __construct(\Redis $redis) {
$this->redis = $redis;
}
public function isEnabled(string $feature, ?string $userId = null): bool {
// 支持用户级别
if ($userId && $this->redis->sIsMember("feature:{$feature}:users", $userId)) {
return true;
}
// 全局控制
return (bool)$this->redis->get("feature:{$feature}");
}
public function enableForUser(string $feature, string $userId): void {
$this->redis->sAdd("feature:{$feature}:users", $userId);
}
public function enableGlobally(string $feature): void {
$this->redis->set("feature:{$feature}", 1);
}
}
// 使用
$flag = new DynamicFeatureFlag($redis);
if ($flag->isEnabled('beta_features', $currentUser->id)) {
// Beta功能
}
高级方案(包含实验分组)
class FeatureFlagService {
private $customers = [];
public function isEnabled(string $feature, ?int $userId = null): bool {
$default = [
'percentage' => 0,
'users' => [],
'enabled' => false
];
$config = $this->getFeatureConfig($feature) ?? $default;
// 特定用户开启
if ($userId && in_array($userId, $config['users'])) {
return true;
}
// 按百分比随机开启
if ($config['percentage'] > 0 && $userId) {
$hash = crc32("{$feature}:{$userId}");
return ($hash % 100) < $config['percentage'];
}
return $config['enabled'];
}
}
// 配置
$features = [
'new_dashboard' => [
'percentage' => 50, // 50%的用户
'users' => [1, 5, 10], // 指定用户
'enabled' => false
]
];
最佳实践建议
- 分层控制:全局 → 环境 → 用户
- 及时清理:功能稳定后移除旧代码
- 日志记录:记录功能开关的使用情况
- 监控告警:对关键功能开关设置监控
- 安全第一:重要功能默认关闭
简单封装示例
class Feature {
private static $config = [];
public static function init(array $config): void {
self::$config = $config;
}
public static function isEnabled(string $name): bool {
// 支持嵌套配置
$value = self::$config[$name] ?? null;
if (is_array($value)) {
return $value['enabled'] ?? false;
}
return (bool)$value;
}
public static function all(): array {
return self::$config;
}
}
// 初始化
Feature::init([
'new_ui' => true,
'payment_v2' => ['enabled' => false, 'version' => '2.0'],
'export_csv' => true
]);
// 使用
if (Feature::isEnabled('new_ui')) {
// 新UI代码
}
选择哪种方案取决于你的需求:
- 简单项目:配置文件方案即可
- 需要动态控制:数据库或 Redis 方案
- 大型项目:第三方库或自研服务
- 需要 A/B 测试:带百分比的高级方案