Laravel Pennant
介绍
Laravel Pennant 是一个简单且轻量的功能标志包,没有多余的东西。功能标志使您能够自信地逐步推出新的应用程序功能,进行 A/B 测试新界面设计,补充基于主干的开发策略,等等。
安装
首先,使用 Composer 包管理器将 Pennant 安装到您的项目中:
composer require laravel/pennant
接下来,您应该使用 vendor:publish
Artisan 命令发布 Pennant 的配置和迁移文件:
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
最后,您应该运行应用程序的数据库迁移。这将创建一个 features
表,Pennant 使用它来支持其 database
驱动:
php artisan migrate
配置
发布 Pennant 的资产后,其配置文件将位于 config/pennant.php
。此配置文件允许您指定 Pennant 用于存储已解析功能标志值的默认存储机制。
Pennant 支持通过 array
驱动在内存数组中存储已解析的功能标志值。或者,Pennant 可以通过 database
驱动在关系数据库中持久存储已解析的功能标志值,这是 Pennant 使用的默认存储机制。
定义功能
要定义功能,您可以使用 Feature
facade 提供的 define
方法。您需要为功能提供一个名称,以及一个将在解析功能的初始值时调用的闭包。
通常,功能是在服务提供者中使用 Feature
facade 定义的。闭包将接收功能检查的“范围”。最常见的是,范围是当前认证的用户。在此示例中,我们将为应用程序用户逐步推出新 API 定义一个功能:
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* 启动任何应用程序服务。
*/
public function boot(): void
{
Feature::define('new-api', fn (User $user) => match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
});
}
}
如您所见,我们为功能定义了以下规则:
- 所有内部团队成员都应该使用新 API。
- 任何高流量客户不应使用新 API。
- 否则,功能应以 1/100 的概率随机分配给用户。
第一次为给定用户检查 new-api
功能时,闭包的结果将由存储驱动存储。下次对同一用户检查功能时,将从存储中检索值,闭包将不会被调用。
为了方便起见,如果功能定义仅返回一个彩票,您可以完全省略闭包:
Feature::define('site-redesign', Lottery::odds(1, 1000));
基于类的功能
Pennant 还允许您定义基于类的功能。与基于闭包的功能定义不同,无需在服务提供者中注册基于类的功能。要创建基于类的功能,您可以调用 pennant:feature
Artisan 命令。默认情况下,功能类将放置在应用程序的 app/Features
目录中:
php artisan pennant:feature NewApi
编写功能类时,您只需定义一个 resolve
方法,该方法将在给定范围内解析功能的初始值时调用。范围通常是当前认证的用户:
<?php
namespace App\Features;
use Illuminate\Support\Lottery;
class NewApi
{
/**
* 解析功能的初始值。
*/
public function resolve(User $user): mixed
{
return match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
};
}
}
功能类是通过 容器 解析的,因此您可以在需要时将依赖项注入到功能类的构造函数中。
自定义存储的功能名称
默认情况下,Pennant 将存储功能类的完全限定类名。如果您希望将存储的功能名称与应用程序的内部结构解耦,可以在功能类上指定一个 $name
属性。此属性的值将代替类名存储:
<?php
namespace App\Features;
class NewApi
{
/**
* 功能的存储名称。
*
* @var string
*/
public $name = 'new-api';
// ...
}
检查功能
要确定功能是否处于活动状态,您可以使用 Feature
facade 上的 active
方法。默认情况下,功能是针对当前认证的用户进行检查的:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
class PodcastController
{
/**
* 显示资源的列表。
*/
public function index(Request $request): Response
{
return Feature::active('new-api')
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
}
// ...
}
虽然功能是针对当前认证的用户进行检查的,但您可以轻松地针对其他用户或 范围 检查功能。为此,请使用 Feature
facade 提供的 for
方法:
return Feature::for($user)->active('new-api')
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
Pennant 还提供了一些额外的便捷方法,这些方法在确定功能是否处于活动状态时可能会很有用:
// 确定所有给定功能是否处于活动状态...
Feature::allAreActive(['new-api', 'site-redesign']);
// 确定是否有任何给定功能处于活动状态...
Feature::someAreActive(['new-api', 'site-redesign']);
// 确定功能是否处于非活动状态...
Feature::inactive('new-api');
// 确定所有给定功能是否处于非活动状态...
Feature::allAreInactive(['new-api', 'site-redesign']);
// 确定是否有任何给定功能处于非活动状态...
Feature::someAreInactive(['new-api', 'site-redesign']);
在 HTTP 上下文之外使用 Pennant 时,例如在 Artisan 命令或队列作业中,您通常应该 显式指定功能的范围。或者,您可以定义一个 默认范围,该范围同时考虑到已认证的 HTTP 上下文和未认证的上下文。
检查基于类的功能
对于基于类的功能,您应该在检查功能时提供类名:
<?php
namespace App\Http\Controllers;
use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
class PodcastController
{
/**
* 显示资源的列表。
*/
public function index(Request $request): Response
{
return Feature::active(NewApi::class)
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
}
// ...
}
条件执行
when
方法可用于流畅地执行给定的闭包,如果功能处于活动状态。此外,可以提供第二个闭包,如果功能处于非活动状态,将执行该闭包:
<?php
namespace App\Http\Controllers;
use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
class PodcastController
{
/**
* 显示资源的列表。
*/
public function index(Request $request): Response
{
return Feature::when(NewApi::class,
fn () => $this->resolveNewApiResponse($request),
fn () => $this->resolveLegacyApiResponse($request),
);
}
// ...
}
unless
方法是 when
方法的反义词,在功能处于非活动状态时执行第一个闭包:
return Feature::unless(NewApi::class,
fn () => $this->resolveLegacyApiResponse($request),
fn () => $this->resolveNewApiResponse($request),
);
HasFeatures
Trait
Pennant 的 HasFeatures
trait 可以添加到应用程序的 User
模型(或任何具有功能的模型)中,以便提供一种流畅、方便的方法来直接从模型检查功能:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Pennant\Concerns\HasFeatures;
class User extends Authenticatable
{
use HasFeatures;
// ...
}
一旦将 trait 添加到模型中,您可以通过调用 features
方法轻松检查功能:
if ($user->features()->active('new-api')) {
// ...
}
当然,features
方法提供了许多其他方便的方法来与功能交互:
// 值...
$value = $user->features()->value('purchase-button')
$values = $user->features()->values(['new-api', 'purchase-button']);
// 状态...
$user->features()->active('new-api');
$user->features()->allAreActive(['new-api', 'server-api']);
$user->features()->someAreActive(['new-api', 'server-api']);
$user->features()->inactive('new-api');
$user->features()->allAreInactive(['new-api', 'server-api']);
$user->features()->someAreInactive(['new-api', 'server-api']);
// 条件执行...
$user->features()->when('new-api',
fn () => /* ... */,
fn () => /* ... */,
);
$user->features()->unless('new-api',
fn () => /* ... */,
fn () => /* ... */,
);
Blade 指令
为了使在 Blade 中检查功能成为无缝体验,Pennant 提供了一个 @feature
指令:
@feature('site-redesign')
<!-- 'site-redesign' 处于活动状态 -->
@else
<!-- 'site-redesign' 处于非活动状态 -->
@endfeature
中间件
Pennant 还包括一个 中间件,可用于在路由被调用之前验证当前认证的用户是否有权访问某个功能。您可以将中间件分配给路由,并指定访问路由所需的功能。如果指定的任何功能对当前认证的用户处于非活动状态,路由将返回 400 Bad Request
HTTP 响应。可以将多个功能传递给静态 using
方法。
use Illuminate\Support\Facades\Route;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
Route::get('/api/servers', function () {
// ...
})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api'));
自定义响应
如果您希望自定义中间件在列出的功能之一处于非活动状态时返回的响应,可以使用 EnsureFeaturesAreActive
中间件提供的 whenInactive
方法。通常,此方法应在应用程序的服务提供者之一的 boot
方法中调用:
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
/**
* 启动任何应用程序服务。
*/
public function boot(): void
{
EnsureFeaturesAreActive::whenInactive(
function (Request $request, array $features) {
return new Response(status: 403);
}
);
// ...
}
内存缓存
在检查功能时,Pennant 将创建结果的内存缓存。如果您使用的是 database
驱动,这意味着在单个请求中重新检查相同的功能标志不会触发额外的数据库查询。这也确保了功能在请求期间具有一致的结果。
如果您需要手动刷新内存缓存,可以使用 Feature
facade 提供的 flushCache
方法:
Feature::flushCache();
范围
指定范围
如前所述,功能通常是针对当前认证的用户进行检查的。然而,这可能并不总是适合您的需求。因此,可以通过 Feature
facade 的 for
方法指定您希望检查给定功能的范围:
return Feature::for($user)->active('new-api')
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
当然,功能范围不限于“用户”。假设您构建了一个新的计费体验,您希望将其推出给整个团队而不是单个用户。也许您希望较旧的团队比较新的团队更慢地推出。您的功能解析闭包可能如下所示:
use App\Models\Team;
use Carbon\Carbon;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;
Feature::define('billing-v2', function (Team $team) {
if ($team->created_at->isAfter(new Carbon('1st Jan, 2023'))) {
return true;
}
if ($team->created_at->isAfter(new Carbon('1st Jan, 2019'))) {
return Lottery::odds(1 / 100);
}
return Lottery::odds(1 / 1000);
});
您会注意到我们定义的闭包并不期望 User
,而是期望 Team
模型。要确定此功能是否对用户的团队处于活动状态,您应该将团队传递给 Feature
facade 提供的 for
方法:
if (Feature::for($user->team)->active('billing-v2')) {
return redirect()->to('/billing/v2');
}
// ...
默认范围
还可以自定义 Pennant 用于检查功能的默认范围。例如,也许您所有的功能都是针对当前认证用户的团队进行检查的,而不是用户。与其每次检查功能时都调用 Feature::for($user->team)
,您可以指定团队作为默认范围。通常,这应该在应用程序的服务提供者之一中完成:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* 启动任何应用程序服务。
*/
public function boot(): void
{
Feature::resolveScopeUsing(fn ($driver) => Auth::user()?->team);
// ...
}
}
如果没有通过 for
方法显式提供范围,功能检查将使用当前认证用户的团队作为默认范围:
Feature::active('billing-v2');
// 现在等同于...
Feature::for($user->team)->active('billing-v2');
可空范围
如果您在检查功能时提供的范围为 null
,并且功能的定义不支持通过可空类型或在联合类型中包含 null
,Pennant 将自动返回 false
作为功能的结果值。
因此,如果您传递给功能的范围可能为 null
,并且您希望功能的值解析器被调用,您应该在功能的定义中考虑到这一点。null
范围可能发生在您在 Artisan 命令、队列作业或未认证路由中检查功能时。由于在这些上下文中通常没有认证用户,默认范围将为 null
。
如果您并不总是 显式指定功能范围,那么您应该确保范围的类型是“可空的”,并在功能定义逻辑中处理 null
范围值:
use App\Models\User;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;
Feature::define('new-api', fn (User $user) => match (true) {
Feature::define('new-api', fn (User|null $user) => match (true) {
$user === null => true,
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
});
识别范围
Pennant 的内置 array
和 database
存储驱动知道如何正确存储所有 PHP 数据类型以及 Eloquent 模型的范围标识符。然而,如果您的应用程序使用第三方 Pennant 驱动,该驱动可能不知道如何正确存储 Eloquent 模型或应用程序中的其他自定义类型的标识符。
鉴于此,Pennant 允许您通过在应用程序中用作 Pennant 范围的对象上实现 FeatureScopeable
合约来格式化范围值以进行存储。
例如,假设您在单个应用程序中使用两个不同的功能驱动:内置的 database
驱动和第三方“Flag Rocket”驱动。“Flag Rocket”驱动不知道如何正确存储 Eloquent 模型。相反,它需要一个 FlagRocketUser
实例。通过实现 FeatureScopeable
合约定义的 toFeatureIdentifier
,我们可以自定义提供给应用程序使用的每个驱动的可存储范围值:
<?php
namespace App\Models;
use FlagRocket\FlagRocketUser;
use Illuminate\Database\Eloquent\Model;
use Laravel\Pennant\Contracts\FeatureScopeable;
class User extends Model implements FeatureScopeable
{
/**
* 将对象转换为给定驱动的功能范围标识符。
*/
public function toFeatureIdentifier(string $driver): mixed
{
return match($driver) {
'database' => $this,
'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id),
};
}
}
序列化范围
默认情况下,Pennant 在存储与 Eloquent 模型关联的功能时将使用完全限定类名。如果您已经在使用 Eloquent morph map,您可以选择让 Pennant 也使用 morph map 来将存储的功能与应用程序结构解耦。
要实现这一点,在服务提供者中定义 Eloquent morph map 后,您可以调用 Feature
facade 的 useMorphMap
方法:
use Illuminate\Database\Eloquent\Relations\Relation;
use Laravel\Pennant\Feature;
Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);
Feature::useMorphMap();
丰富的功能值
到目前为止,我们主要展示了功能处于二进制状态,意味着它们要么“活动”,要么“非活动”,但 Pennant 也允许您存储丰富的值。
例如,假设您正在测试应用程序“立即购买”按钮的三种新颜色。您可以从功能定义中返回一个字符串,而不是返回 true
或 false
:
use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;
Feature::define('purchase-button', fn (User $user) => Arr::random([
'blue-sapphire',
'seafoam-green',
'tart-orange',
]));
您可以使用 value
方法检索 purchase-button
功能的值:
$color = Feature::value('purchase-button');
Pennant 的包含 Blade 指令也使得根据功能的当前值有条件地渲染内容变得容易:
@feature('purchase-button', 'blue-sapphire')
<!-- 'blue-sapphire' 处于活动状态 -->
@elsefeature('purchase-button', 'seafoam-green')
<!-- 'seafoam-green' 处于活动状态 -->
@elsefeature('purchase-button', 'tart-orange')
<!-- 'tart-orange' 处于活动状态 -->
@endfeature
使用丰富的值时,重要的是要知道当功能具有任何值而不是 false
时,它被视为“活动”。
调用 条件 when
方法时,功能的丰富值将提供给第一个闭包:
Feature::when('purchase-button',
fn ($color) => /* ... */,
fn () => /* ... */,
);
同样,调用条件 unless
方法时,功能的丰富值将提供给可选的第二个闭包:
Feature::unless('purchase-button',
fn () => /* ... */,
fn ($color) => /* ... */,
);
检索多个功能
values
方法允许为给定范围检索多个功能:
Feature::values(['billing-v2', 'purchase-button']);
// [
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// ]
或者,您可以使用 all
方法检索给定范围内所有定义的功能的值:
Feature::all();
// [
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// 'site-redesign' => true,
// ]
然而,基于类的功能是动态注册的,Pennant 在它们被显式检查之前不知道它们。这意味着如果它们在当前请求期间尚未被检查,您的应用程序的基于类的功能可能不会出现在 all
方法返回的结果中。
如果您希望在使用 all
方法时始终包含功能类,可以使用 Pennant 的功能发现功能。要开始,请在应用程序的服务提供者之一中调用 discover
方法:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* 启动任何应用程序服务。
*/
public function boot(): void
{
Feature::discover();
// ...
}
}
discover
方法将注册应用程序 app/Features
目录中的所有功能类。all
方法现在将包括这些类在其结果中,无论它们是否在当前请求期间被检查:
Feature::all();
// [
// 'App\Features\NewApi' => true,
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// 'site-redesign' => true,
// ]
预加载
虽然 Pennant 为单个请求保留了所有已解析功能的内存缓存,但仍然可能遇到性能问题。为了解决这个问题,Pennant 提供了预加载功能值的能力。
为了说明这一点,假设我们在循环中检查功能是否处于活动状态:
use Laravel\Pennant\Feature;
foreach ($users as $user) {
if (Feature::for($user)->active('notifications-beta')) {
$user->notify(new RegistrationSuccess);
}
}
假设我们使用的是数据库驱动,这段代码将为循环中的每个用户执行一个数据库查询 - 可能执行数百个查询。然而,使用 Pennant 的 load
方法,我们可以通过为用户或范围集合预加载功能值来消除这种潜在的性能瓶颈:
Feature::for($users)->load(['notifications-beta']);
foreach ($users as $user) {
if (Feature::for($user)->active('notifications-beta')) {
$user->notify(new RegistrationSuccess);
}
}
要仅在尚未加载功能值时加载它们,可以使用 loadMissing
方法:
Feature::for($users)->loadMissing([
'new-api',
'purchase-button',
'notifications-beta',
]);
更新值
当功能的值第一次被解析时,底层驱动将结果存储在存储中。这通常是必要的,以确保用户在请求之间的一致体验。然而,有时,您可能希望手动更新功能的存储值。
为此,您可以使用 activate
和 deactivate
方法将功能切换为“开”或“关”:
use Laravel\Pennant\Feature;
// 激活默认范围的功能...
Feature::activate('new-api');
// 为给定范围停用功能...
Feature::for($user->team)->deactivate('billing-v2');
还可以通过向 activate
方法提供第二个参数来手动设置功能的丰富值:
Feature::activate('purchase-button', 'seafoam-green');
要指示 Pennant 忘记功能的存储值,可以使用 forget
方法。当再次检查功能时,Pennant 将从其功能定义中解析功能的值:
Feature::forget('purchase-button');
批量更新
要批量更新存储的功能值,可以使用 activateForEveryone
和 deactivateForEveryone
方法。
例如,假设您现在对 new-api
功能的稳定性充满信心,并且已经确定了结账流程中“购买按钮”的最佳颜色 - 您可以相应地更新所有用户的存储值:
use Laravel\Pennant\Feature;
Feature::activateForEveryone('new-api');
Feature::activateForEveryone('purchase-button', 'seafoam-green');
或者,您可以为所有用户停用功能:
Feature::deactivateForEveryone('new-api');
这只会更新 Pennant 的存储驱动存储的已解析功能值。您还需要更新应用程序中的功能定义。
清除功能
有时,清除存储中的整个功能可能很有用。这通常是必要的,如果您已从应用程序中删除功能,或者您对功能的定义进行了调整,您希望将其推出给所有用户。
您可以使用 purge
方法删除功能的所有存储值:
// 清除单个功能...
Feature::purge('new-api');
// 清除多个功能...
Feature::purge(['new-api', 'purchase-button']);
如果您希望清除 所有 存储中的功能,可以在没有任何参数的情况下调用 purge
方法:
Feature::purge();
由于在应用程序的部署管道中清除功能可能很有用,Pennant 包含一个 pennant:purge
Artisan 命令,该命令将从存储中清除提供的功能:
php artisan pennant:purge new-api
php artisan pennant:purge new-api purchase-button
还可以清除所有功能 除了 给定功能列表中的那些。例如,假设您想清除所有功能,但保留“new-api”和“purchase-button”功能的值。为此,您可以将这些功能名称传递给 --except
选项:
php artisan pennant:purge --except=new-api --except=purchase-button
为了方便起见,pennant:purge
命令还支持 --except-registered
标志。此标志表示应清除所有功能,除了在服务提供者中显式注册的那些:
php artisan pennant:purge --except-registered
测试
在测试与功能标志交互的代码时,控制功能标志在测试中返回值的最简单方法是简单地重新定义功能。例如,假设您在应用程序的服务提供者之一中定义了以下功能:
use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;
Feature::define('purchase-button', fn () => Arr::random([
'blue-sapphire',
'seafoam-green',
'tart-orange',
]));
要在测试中修改功能的返回值,可以在测试开始时重新定义功能。即使服务提供者中仍然存在 Arr::random()
实现,以下测试也将始终通过:
use Laravel\Pennant\Feature;
public function test_it_can_control_feature_values()
{
Feature::define('purchase-button', 'seafoam-green');
$this->assertSame('seafoam-green', Feature::value('purchase-button'));
}
同样的方法也可以用于基于类的功能:
use App\Features\NewApi;
use Laravel\Pennant\Feature;
public function test_it_can_control_feature_values()
{
Feature::define(NewApi::class, true);
$this->assertTrue(Feature::value(NewApi::class));
}
如果您的功能返回 Lottery
实例,有一些有用的 测试助手可用。
存储配置
您可以通过在应用程序的 phpunit.xml
文件中定义 PENNANT_STORE
环境变量来配置 Pennant 在测试期间使用的存储:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
<!-- ... -->
<php>
<env name="PENNANT_STORE" value="array"/>
<!-- ... -->
</php>
</phpunit>
添加自定义 Pennant 驱动
实现驱动
如果 Pennant 的现有存储驱动都不适合您的应用程序需求,您可以编写自己的存储驱动。您的自定义驱动应实现 Laravel\Pennant\Contracts\Driver
接口:
<?php
namespace App\Extensions;
use Laravel\Pennant\Contracts\Driver;
class RedisFeatureDriver implements Driver
{
public function define(string $feature, callable $resolver): void {}
public function defined(): array {}
public function getAll(array $features): array {}
public function get(string $feature, mixed $scope): mixed {}
public function set(string $feature, mixed $scope, mixed $value): void {}
public function setForAllScopes(string $feature, mixed $value): void {}
public function delete(string $feature, mixed $scope): void {}
public function purge(array|null $features): void {}
}
现在,我们只需使用 Redis 连接实现这些方法。有关如何实现每个方法的示例,请查看 Pennant 源代码 中的 Laravel\Pennant\Drivers\DatabaseDriver
Laravel 不附带包含扩展的目录。您可以将它们放置在任何您喜欢的地方。在此示例中,我们创建了一个 Extensions
目录来容纳 RedisFeatureDriver
。
注册驱动
一旦实现了驱动,您就可以准备好将其注册到 Laravel。要向 Pennant 添加其他驱动,可以使用 Feature
facade 提供的 extend
方法。您应该从应用程序的 服务提供者 的 boot
方法中调用 extend
方法:
<?php
namespace App\Providers;
use App\Extensions\RedisFeatureDriver;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* 注册任何应用程序服务。
*/
public function register(): void
{
// ...
}
/**
* 启动任何应用程序服务。
*/
public function boot(): void
{
Feature::extend('redis', function (Application $app) {
return new RedisFeatureDriver($app->make('redis'), $app->make('events'), []);
});
}
}
注册驱动后,您可以在应用程序的 config/pennant.php
配置文件中使用 redis
驱动:
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => null,
],
// ...
],
事件
Pennant 分派各种事件,这些事件在跟踪应用程序中的功能标志时可能很有用。
Laravel\Pennant\Events\RetrievingKnownFeature
此事件在请求期间首次为特定范围检索已知功能时分派。此事件在创建和跟踪应用程序中使用的功能标志的指标时可能很有用。
Laravel\Pennant\Events\RetrievingUnknownFeature
此事件在请求期间首次为特定范围检索未知功能时分派。如果您打算删除功能标志,但可能不小心在应用程序中留下了一些零散的引用,此事件可能很有用。
例如,您可能会发现监听此事件并在发生时 report
或抛出异常很有用:
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use Laravel\Pennant\Events\RetrievingUnknownFeature;
class EventServiceProvider extends ServiceProvider
{
/**
* 为应用程序注册任何其他事件。
*/
public function boot(): void
{
Event::listen(function (RetrievingUnknownFeature $event) {
report("Resolving unknown feature [{$event->feature}].");
});
}
}
Laravel\Pennant\Events\DynamicallyDefiningFeature
此事件在请求期间首次动态检查基于类的功能时分派。