本文目录导读:

我来详细介绍 Laravel 特性开关(Feature Flags)的几种实现方式:
使用 Laravel Pennant(官方推荐)
Laravel 8.x 及以上版本提供了 Pennant 包,是官方推荐的特性开关解决方案。
安装配置
composer require laravel/pennant php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider" php artisan migrate
定义特性
// app/Features/NewCheckout.php
use Laravel\Pennant\Contracts\Feature;
class NewCheckout implements Feature
{
public function resolve(mixed $scope): mixed
{
// 基于用户分组
if ($scope instanceof User) {
return $scope->isBetaTester();
}
// 基于百分比
return random_int(1, 100) <= 50;
}
}
使用特性开关
// 检查特性是否开启
if (Feature::active('new-checkout')) {
// 使用新结账流程
}
// 基于用户检查
if (Feature::for($user)->active('new-checkout')) {
// 该用户可以使用新功能
}
// 在 Blade 模板中使用
@feature('new-checkout')
<div>新结账界面</div>
@endfeature
基于数据库的特性开关
创建表结构和模型
// 迁移文件
Schema::create('feature_flags', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->boolean('enabled')->default(false);
$table->json('conditions')->nullable();
$table->timestamps();
});
// FeatureFlag 模型
class FeatureFlag extends Model
{
protected $fillable = ['name', 'enabled', 'conditions'];
protected $casts = [
'enabled' => 'boolean',
'conditions' => 'array'
];
}
辅助类
namespace App\Services;
class FeatureFlagService
{
private static $cache = [];
public static function isEnabled(string $feature, $user = null): bool
{
$flag = self::getFlag($feature);
if (!$flag || !$flag->enabled) {
return false;
}
// 检查用户条件
if ($user && $flag->conditions) {
return self::checkConditions($flag->conditions, $user);
}
return true;
}
private static function getFlag(string $feature): ?FeatureFlag
{
if (!isset(self::$cache[$feature])) {
self::$cache[$feature] = FeatureFlag::where('name', $feature)->first();
}
return self::$cache[$feature];
}
private static function checkConditions(array $conditions, $user): bool
{
foreach ($conditions as $type => $value) {
switch ($type) {
case 'user_ids':
if (in_array($user->id, $value)) return true;
break;
case 'roles':
if ($user->hasRole($value)) return true;
break;
case 'percentage':
if (crc32($user->email) % 100 < $value) return true;
break;
}
}
return false;
}
}
基于配置文件的方式
配置文件
// config/features.php
return [
'new_checkout' => [
'enabled' => env('FEATURE_NEW_CHECKOUT', false),
'users' => [], // 允许的用户邮箱
'roles' => ['admin', 'beta_tester'],
'percentage' => 50, // 50%的用户
],
'dark_mode' => [
'enabled' => true,
'users' => [],
'roles' => [],
'percentage' => 100,
],
];
服务类
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class FeatureService
{
public static function isActive(string $feature, $user = null): bool
{
$config = config("features.{$feature}");
if (!$config || !$config['enabled']) {
return false;
}
if (!$user) {
return $config['enabled'];
}
// 检查用户白名单
if (!empty($config['users'])) {
if (in_array($user->email, $config['users'])) {
return true;
}
}
// 检查角色
if (!empty($config['roles'])) {
foreach ($config['roles'] as $role) {
if ($user->hasRole($role)) {
return true;
}
}
}
// 百分比控制
if (!empty($config['percentage'])) {
$hash = crc32($user->email . $feature);
return ($hash % 100) < $config['percentage'];
}
return true;
}
public static function all(): array
{
$features = config('features');
$result = [];
foreach ($features as $name => $config) {
$result[$name] = [
'enabled' => $config['enabled'],
'active' => self::isActive($name),
];
}
return $result;
}
}
使用环境变量(简单场景)
// .env 或 .env.development
FEATURE_NEW_CHECKOUT=true
FEATURE_DARK_MODE=false
FEATURE_ANALYTICS_V2=true
// 使用
if (env('FEATURE_NEW_CHECKOUT', false)) {
// 新功能代码
}
自定义中间件
namespace App\Http\Middleware;
use Closure;
use App\Services\FeatureService;
class FeatureFlag
{
public function handle($request, Closure $next, $feature)
{
if (!FeatureService::isActive($feature, $request->user())) {
abort(404); // 或返回其他响应
}
return $next($request);
}
}
// 在路由中使用
Route::middleware(['feature:new_checkout'])->group(function () {
Route::get('/checkout/new', 'CheckoutController@new');
});
Blade 指令
// AppServiceProvider 中注册
use Illuminate\Support\Facades\Blade;
use App\Services\FeatureService;
Blade::if('feature', function ($feature) {
return FeatureService::isActive($feature, auth()->user());
});
// 在模板中使用
@feature('new_checkout')
<!-- 新结账界面 -->
@else
<!-- 原有结账界面 -->
@endfeature
最佳实践建议
-
命名规范:使用清晰的命名约定,如
feature-name或feature_name -
清理策略:定期清理不再需要的特性开关
-
监控和日志:记录特性使用情况
-
分级策略:从内测到小范围,再到全量发布
-
缓存:对不频繁变化的特性开关进行缓存
// 缓存示例
public static function isActive(string $feature, $user = null): bool
{
return Cache::remember("feature.{$feature}." . ($user->id ?? 'global'), 3600, function () use ($feature, $user) {
return self::checkFeature($feature, $user);
});
}
根据项目复杂度选择合适的实现方式,简单项目用配置或环境变量,复杂项目建议使用 Pennant 或数据库方案。