Laravel 三角旗
- Introduction
- Installation
- Configuration
- 定义特征
- 检查功能
- Scope
- 丰富的特征值
- 检索多个特征
- 预加载
- 更新值
- Testing
- 添加自定义三角旗驱动程序
- Events
Introduction
Laravel 三角旗 是一个简单轻量级的功能标志包 - 没有多余的东西。功能标志使您能够自信地逐步推出新的应用程序功能、A/B 测试新的界面设计、补充基于主干的开发策略等等。
Installation
首先,使用 Composer 包管理器将 Pennant 安装到你的项目中:
composer require laravel/pennant
接下来,您应该使用vendor:publish
工匠命令:
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
最后,您应该运行应用程序的数据库迁移。这将创建一个features
Pennant 用来为其供电的桌子database
司机:
php artisan migrate
Configuration
发布 Pennant 的资产后,其配置文件位于config/pennant.php
.此配置文件允许您指定默认存储机制,Pennant 将使用该机制来存储已解析的特征标志值。
Pennant 支持通过array
司机。或者,Pennant 可以通过database
驱动程序,这是 Pennant 使用的默认存储机制。
定义特征
要定义功能,您可以使用define
提供的方法Feature
正面。您将需要为该功能提供一个名称,以及一个将被调用以解析该功能初始值的闭包。
通常,功能是在服务提供者中使用Feature
正面。闭包将接收功能检查的“范围”。最常见的是,范围是当前经过身份验证的用户。在此示例中,我们将定义一个功能,用于向我们的应用程序用户逐步推出新的 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
{
/**
* Bootstrap any application services.
*/
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。
- 否则,该功能应该随机分配给有 100 分之一活跃机会的用户。
第一次new-api
为给定的用户检查功能,关闭的结果将由存储驱动程序存储。下次针对同一用户检查该功能时,将从存储中检索该值,并且不会调用闭包。
为了方便起见,如果特征定义只返回彩票,您可以完全省略闭包:
Feature::define('site-redesign', Lottery::odds(1, 1000));
基于类别的功能
Pennant 还允许您定义基于类别的功能。与基于闭包的特征定义不同,不需要在服务提供者中注册基于类的特征。要创建基于类的功能,您可以调用pennant:feature
工匠命令。默认情况下,要素类将放置在您的应用程序的app/Features
目录:
php artisan pennant:feature NewApi
编写要素类时,只需定义一个resolve
方法,将调用该方法来解析给定范围的功能初始值。同样,范围通常是当前经过身份验证的用户:
<?php
namespace App\Features;
use Illuminate\Support\Lottery;
class NewApi
{
/**
* Resolve the feature's initial value.
*/
public function resolve(User $user): mixed
{
return match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
};
}
}
Note 要素类通过container,因此您可以在需要时将依赖项注入要素类的构造函数。
检查功能
要确定某个功能是否处于活动状态,您可以使用active
上的方法Feature
正面。默认情况下,会针对当前经过身份验证的用户检查功能:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
class PodcastController
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): Response
{
return Feature::active('new-api')
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
}
// ...
}
尽管默认情况下会针对当前经过身份验证的用户检查功能,但您可以轻松地针对其他用户检查功能或scope.为此,请使用for
提供的方法Feature
正面:
return Feature::for($user)->active('new-api')
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
Pennant 还提供了一些额外的便利方法,这些方法在确定某个功能是否处于活动状态时可能很有用:
// Determine if all of the given features are active...
Feature::allAreActive(['new-api', 'site-redesign']);
// Determine if any of the given features are active...
Feature::someAreActive(['new-api', 'site-redesign']);
// Determine if a feature is inactive...
Feature::inactive('new-api');
// Determine if all of the given features are inactive...
Feature::allAreInactive(['new-api', 'site-redesign']);
// Determine if any of the given features are inactive...
Feature::someAreInactive(['new-api', 'site-redesign']);
Note 在 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
{
/**
* Display a listing of the resource.
*/
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
{
/**
* Display a listing of the resource.
*/
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
特征
这锦旗的HasFeatures
可以将特征添加到您的应用程序的User
模型(或任何其他具有特征的模型)提供一种流畅、方便的方式来直接从模型中检查特征:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Pennant\Concerns\HasFeatures;
class User extends Authenticatable
{
use HasFeatures;
// ...
}
一旦特征被添加到你的模型中,你可以通过调用features
方法:
if ($user->features()->active('new-api')) {
// ...
}
当然,features
方法提供了对许多其他与功能交互的方便方法的访问:
// Values...
$value = $user->features()->value('purchase-button')
$values = $user->features()->values(['new-api', 'purchase-button']);
// State...
$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']);
// Conditional execution...
$user->features()->when('new-api',
fn () => /* ... */,
fn () => /* ... */,
);
$user->features()->unless('new-api',
fn () => /* ... */,
fn () => /* ... */,
);
刀片指令
为了让 Blade 中的检查功能成为无缝体验,Pennant 提供了一个@feature
指示:
@feature('site-redesign')
<!-- 'site-redesign' is active -->
@else
<!-- 'site-redesign' is inactive -->
@endfeature
Middleware
三角旗还包括一个middleware 可用于在调用路由之前验证当前经过身份验证的用户是否有权访问某项功能。首先,您应该为EnsureFeaturesAreActive
应用程序的中间件app/Http/Kernel.php
文件:
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
protected $middlewareAliases = [
// ...
'features' => EnsureFeaturesAreActive::class,
];
接下来,您可以将中间件分配给路由并指定访问该路由所需的功能。如果任何指定的功能对于当前经过身份验证的用户来说是不活动的,一个400 Bad Request
路由将返回 HTTP 响应。可以使用逗号分隔列表指定多个功能:
Route::get('/api/servers', function () {
// ...
})->middleware(['features:new-api,servers-api']);
自定义响应
如果您想自定义中间件在列出的功能之一处于非活动状态时返回的响应,您可以使用whenInactive
提供的方法EnsureFeaturesAreActive
中间件。通常,应在boot
您的应用程序服务提供商之一的方法:
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
EnsureFeaturesAreActive::whenInactive(
function (Request $request, array $features) {
return new Response(status: 403);
}
);
// ...
}
内存缓存
检查特征时,Pennant 将创建结果的内存缓存。如果您正在使用database
驱动程序,这意味着在单个请求中重新检查相同的功能标志将不会触发额外的数据库查询。这也确保该功能在请求期间具有一致的结果。
如果您需要手动刷新内存缓存,您可以使用flushCache
提供的方法Feature
正面:
Feature::flushCache();
Scope
指定范围
如前所述,通常会针对当前经过身份验证的用户检查功能。但是,这可能并不总是适合您的需要。因此,可以通过Feature
门面的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
模型。要确定此功能是否对用户的团队有效,您应该将团队传递给for
提供的方法Feature
正面:
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
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Feature::resolveScopeUsing(fn ($driver) => Auth::user()?->team);
// ...
}
}
如果没有通过for
方法,功能检查现在将使用当前经过身份验证的用户的团队作为默认范围:
Feature::active('billing-v2');
// Is now equivalent to...
Feature::for($user->team)->active('billing-v2');
可空范围
如果您在检查功能时提供的范围是null
并且特征的定义不支持null
通过可空类型或通过包含null
在联合类型中,Pennant 将自动返回false
作为特征的结果值。
因此,如果您传递给功能的范围可能是null
并且您希望调用该功能的值解析器,您应该在您的功能定义中考虑到这一点。 Anull
如果您检查 Artisan 命令、排队作业或未经身份验证的路由中的功能,则可能会出现范围。由于在这些上下文中通常没有经过身份验证的用户,因此默认范围为null
.
如果你不总是明确指定您的功能范围 那么你应该确保范围的类型是“可空的”并处理null
功能定义逻辑中的范围值:
use App\Models\User;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;
Feature::define('new-api', fn (User $user) => match (true) {// [tl! remove]
Feature::define('new-api', fn (User|null $user) => match (true) {// [tl! add]
$user === null => true,// [tl! add]
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
});
确定范围
彭南特的内置array
和database
存储驱动程序知道如何正确存储所有 PHP 数据类型和 Eloquent 模型的范围标识符。但是,如果您的应用程序使用第三方 Pennant 驱动程序,该驱动程序可能不知道如何在您的应用程序中正确存储 Eloquent 模型或其他自定义类型的标识符。
鉴于此,Pennant 允许您通过实现FeatureScopeable
应用程序中用作 Pennant 范围的对象的合同。
例如,假设您在一个应用程序中使用两个不同的功能驱动程序:内置的database
驱动程序和第三方“Flag Rocket”驱动程序。 “Flag Rocket”驱动程序不知道如何正确存储 Eloquent 模型。相反,它需要一个FlagRocketUser
实例。通过实施toFeatureIdentifier
由定义FeatureScopeable
合同,我们可以自定义提供给我们应用程序使用的每个驱动程序的可存储范围值:
<?php
namespace App\Models;
use FlagRocket\FlagRocketUser;
use Illuminate\Database\Eloquent\Model;
use Laravel\Pennant\Contracts\FeatureScopeable;
class User extends Model implements FeatureScopeable
{
/**
* Cast the object to a feature scope identifier for the given driver.
*/
public function toFeatureIdentifier(string $driver): mixed
{
return match($driver) {
'database' => $this,
'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id),
};
}
}
丰富的特征值
到目前为止,我们主要将特征显示为二进制状态,这意味着它们要么是“活跃的”,要么是“不活跃的”,但 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',
]));
您可以检索的值purchase-button
功能使用value
方法:
$color = Feature::value('purchase-button');
Pennant 包含的 Blade 指令还可以轻松地根据功能的当前值有条件地呈现内容:
@feature('purchase-button', 'blue-sapphire')
<!-- 'blue-sapphire' is active -->
@elsefeature('purchase-button', 'seafoam-green')
<!-- 'seafoam-green' is active -->
@elsefeature('purchase-button', 'tart-orange')
<!-- 'tart-orange' is active -->
@endfeature
Note 当使用丰富的值时,重要的是要知道当一个特征具有除
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
{
/**
* Bootstrap any application services.
*/
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);
}
}
假设我们正在使用数据库驱动程序,此代码将为循环中的每个用户执行数据库查询 - 可能执行数百个查询。然而,使用彭南特的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;
// Activate the feature for the default scope...
Feature::activate('new-api');
// Deactivate the feature for the given scope...
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
功能的稳定性,并已登陆最佳'purchase-button'
结帐流程的颜色 - 您可以相应地更新所有用户的存储值:
use Laravel\Pennant\Feature;
Feature::activateForEveryone('new-api');
Feature::activateForEveryone('purchase-button', 'seafoam-green');
或者,您可以为所有用户停用该功能:
Feature::deactivateForEveryone('new-api');
Note 这只会更新已由 Pennant 的存储驱动程序存储的已解析的特征值。您还需要更新应用程序中的功能定义。
清除特征
有时,从存储中清除整个功能可能很有用。如果您已从应用程序中删除该功能,或者您已对要向所有用户推出的功能定义进行了调整,这通常是必需的。
您可以使用删除功能的所有存储值purge
方法:
// Purging a single feature...
Feature::purge('new-api');
// Purging multiple features...
Feature::purge(['new-api', 'purchase-button']);
如果你想清除all 从存储功能,你可以调用purge
没有任何参数的方法:
Feature::purge();
由于清除功能作为应用程序部署管道的一部分可能很有用,因此 Pennant 包含一个pennant:purge
工匠命令:
php artisan pennant:purge new-api
php artisan pennant:purge new-api purchase-button
Testing
在测试与功能标志交互的代码时,在测试中控制功能标志返回值的最简单方法是简单地重新定义功能。例如,假设您在应用程序的服务提供商之一中定义了以下功能:
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
例如,有一些有用的可用的测试助手.
商店配置
您可以通过定义PENNANT_STORE
应用程序中的环境变量phpunit.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
<!-- ... -->
<php>
<env name="PENNANT_STORE" value="array"/>
<!-- ... -->
</php>
</phpunit>
添加自定义三角旗驱动程序
实施驱动程序
如果 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 连接来实现这些方法中的每一个。有关如何实现这些方法的示例,请查看Laravel\Pennant\Drivers\DatabaseDriver
在里面锦旗源代码
Note Laravel 没有附带一个目录来包含你的扩展。您可以随意将它们放在任何您喜欢的地方。在这个例子中,我们创建了一个
Extensions
存放的目录RedisFeatureDriver
.
注册驱动程序
一旦你的驱动程序被实现,你就可以在 Laravel 中注册它了。要向 Pennant 添加额外的驱动程序,您可以使用extend
提供的方法Feature
正面。你应该打电话给extend
方法来自boot
您的应用程序之一的方法服务提供者:
<?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
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Feature::extend('redis', function (Application $app) {
return new RedisFeatureDriver($app->make('redis'), $app->make('events'), []);
});
}
}
注册驱动程序后,您可以使用redis
应用程序中的驱动程序config/pennant.php
配置文件:
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => null,
],
// ...
],
Events
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
{
/**
* Register any other events for your application.
*/
public function boot(): void
{
Event::listen(function (RetrievingUnknownFeature $event) {
report("Resolving unknown feature [{$event->feature}].");
});
}
}
Laravel\Pennant\Events\DynamicallyDefiningFeature
当在请求期间首次动态检查基于类的功能时,将调度此事件。