Laravel Pennant
介绍
Laravel Pennant 是一个简单轻量的特性标志包,没有臃肿。特性标志使您可以有信心地逐步推出新的应用程序功能,测试新的界面设计,支持基干开发策略等等。
安装
首先,使用 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
门面提供的 define
方法。您需要为该特性提供一个名称以及一个闭包,用于解析该特性的初始值。
通常,特性是在服务提供程序中使用 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。
- 否则,该特性应在具有 1/100 概率激活的用户中随机分配。
首次检查给定用户的 new-api
特性时,存储驱动程序将存储闭包的结果。下一次针对相同用户检查特性时,将从存储中检索该值,不会调用闭包。
为方便起见,如果特性定义仅返回一个 Lottery,您可以完全省略闭包:
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
{
/**
* 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),
};
}
}
特性类是通过容器解析的,因此在需要时可以在特性类的构造函数中注入依赖项。
检查特性
要确定一个特性是否处于活动状态,您可以在 Feature
门面上使用 active
方法。默认情况下,特性针对当前已认证的用户进行检查:
<?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);
}
// ...
}
虽然默认情况下特性针对当前已认证的用户进行检查,但您可以轻松地针对其他用户或范围检查特性。为此,使用 Feature
门面提供的 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
{
/**
* 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 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;
// ...
}
一旦将 HasFeatures
Trait 添加到您的模型中,您可以通过调用 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 指令
为了使在 Blade 中检查特性的体验更加流畅,Pennant提供了一个 @feature
指令:
@feature('site-redesign')
<!-- 'site-redesign' is active -->
@else
<!-- 'site-redesign' is inactive -->
@endfeature
中间件
Pennant 还包括一个中间件,它可以在路由调用之前验证当前认证用户是否有访问功能的权限。首先,您应该将 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']);
自定义响应
如果您希望在未激活列表中的任何一个功能时自定义中间件返回的响应,可以使用 EnsureFeaturesAreActive
中间件提供的 whenInactive
方法。通常,这个方法应该在应用程序的服务提供者的 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
驱动程序,则在单个请求中重新检查相同的功能标志将不会触发额外的数据库查询。这也确保了该功能在请求的持续时间内具有一致的结果。
如果您需要手动刷新内存缓存,可以使用 Feature
门面提供的 flushCache
方法:
Feature::flushCache();
作用域
指定作用域
如前所述,特性通常会针对当前已验证的用户进行检查。但这可能并不总是适合您的需求。因此,您可以通过 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
模型。要确定该特性是否对用户的团队可用,您应该将团队传递给 Feature
门面提供的 for
方法:
if (Feature::for($user->team)->active('billing-v2')) {
return redirect()->to('/billing/v2');
}
// ...
默认作用域
还可以自定义 Pennant 用于检查特性的默认作用域。例如,您可能希望所有特性都针对当前认证用户的团队进行检查,而不是针对用户。您可以在应用程序的服务提供程序中指定此作用域。通常,应该在一个应用程序的服务提供程序中完成这个过程:
<?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
(即不是 nullable
type 或者没有在 union type 中包含null
),那么 Pennant 将自动返回 false
作为特性的结果值。
因此,如果你传递给特性的作用域可能为 null
并且你想要特性值的解析器被调用,你应该在特性定义逻辑中处理 null
范围值。在一个 Artisan 命令、排队作业或未经身份验证的路由中检查特性可能会出现 null
作用域。因为在这些情况下通常没有经过身份验证的用户,所以默认的作用域将为 null
。
如果你不总是明确指定特性作用域,则应确保范围类型为"nullable",并在特性定义逻辑中处理 null
范围值:
标识作用域
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
{
/**
* 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',
]));
您可以使用 value
方法检索 purchase-button
特性的值:
$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
使用丰富值时,重要的是要知道,只要特性具有除
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);
}
}
假设我们使用的是数据库驱动程序,在循环中的每个用户上执行一个数据库查询,可能会执行数百个查询。但是,使用 Pennant 的 load
方法,我们可以通过预加载一组用户或作用域的特性值来消除这种潜在的性能瓶颈:
Feature::for($users)->load(['notifications-beta']);
foreach ($users as $user) {
if (Feature::for($user)->active('notifications-beta')) {
$user->notify(new RegistrationSuccess);
}
}
在这里,我们使用了 load
方法将 notifications-beta
特性值预加载到 $users
集合中。这样,在循环中检查特性是否激活时,就不需要再次查询数据库,从而提高应用性能。
如果只有在特性值尚未加载时才需要加载,可以使用 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
功能的稳定性感到自信,并为结帐流程选定了最佳的 'purchase-button'
颜色 - 您可以相应地更新所有用户的存储值:
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
测试
当测试与特性标记交互的代码时,在您的测试中控制特性标记的返回值的最简单方法是重新定义特性标记。例如,假设您在应用程序的一个服务提供程序中定义了以下特性标记:
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。你需要使用 Feature
门面提供的 extend
方法来添加额外的驱动,然后在一个应用程序服务提供程序的 boot
方法中调用 extend
方法进行注册。
以下是将 RedisFeatureDriver 注册为 redis
驱动的示例:
<?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 boot(): void
{
Feature::extend('redis', function (Application $app) {
return new RedisFeatureDriver($app->make('redis'), $app->make('events'), []);
});
}
}
在上述代码中,我们将 redis
驱动注册到 Pennant 中。返回的闭包接收应用程序实例,用于构建新的 RedisFeatureDriver
实例。
注册后,您可以在应用程序的 config/pennant.php
配置文件中使用 redis
驱动来存储 Pennant 特性状态:
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => null,
],
// ...
],
事件
Pennant 触发各种事件,可以在跟踪应用程序中的功能标志时很有用。
Laravel\Pennant\Events\RetrievingKnownFeature
此事件在针对特定范围的请求中第一次检索已知功能时触发。此事件可用于创建和跟踪应用程序中正在使用的功能标志的指标。
Laravel\Pennant\Events\RetrievingUnknownFeature
此事件在针对特定范围的请求中第一次检索未知功能时触发。如果您打算删除一个功能标志,但可能意外地在应用程序中留下了一些残留的引用,这个事件可能很有用。
例如,您可能会发现在此事件发生时侦听并报告或引发异常很有用:
<?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
此事件在针对类别功能的第一次动态检查期间触发。
No Comments