- はじめに
- インストール
- 設定
- フィーチャーの定義
- フィーチャーのチェック
- 条件付き実行
- HasFeaturesトレイト
- Bladeディレクティブ
- ミドルウェア
- フィーチャーチェックのインターセプト
- メモリ内キャッシュ
- スコープ
- リッチフィーチャー値
- 複数のフィーチャーの取得
- イagerローディング
- 値の更新
- テスト
- カスタムPennantドライバーの追加
- イベント
- Laravel\Pennant\Events\FeatureRetrieved
- Laravel\Pennant\Events\FeatureResolved
- Laravel\Pennant\Events\UnknownFeatureResolved
- Laravel\Pennant\Events\DynamicallyRegisteringFeatureClass
- Laravel\Pennant\Events\UnexpectedNullScopeEncountered
- Laravel\Pennant\Events\FeatureUpdated
- Laravel\Pennant\Events\FeatureUpdatedForAllScopes
- Laravel\Pennant\Events\FeatureDeleted
- Laravel\Pennant\Events\FeaturesPurged
- Laravel\Pennant\Events\AllFeaturesPurged
はじめに
Laravel Pennant は、シンプルで軽量なフィーチャーフラグパッケージです - 不要なものはありません。フィーチャーフラグを使用すると、新しいアプリケーション機能を自信を持って段階的に展開したり、新しいインターフェースデザインをA/Bテストしたり、トランクベースの開発戦略を補完したり、その他多くのことが可能になります。
インストール
まず、Composerパッケージマネージャーを使用してPennantをプロジェクトにインストールします:
composer require laravel/pennant
次に、vendor:publish
Artisanコマンドを使用してPennantの設定ファイルとマイグレーションファイルを公開する必要があります:
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
最後に、アプリケーションのデータベースマイグレーションを実行する必要があります。これにより、Pennantがそのdatabase
ドライバーを動かすために使用するfeatures
テーブルが作成されます:
php artisan migrate
設定
Pennantのアセットを公開した後、その設定ファイルはconfig/pennant.php
にあります。この設定ファイルでは、Pennantが解決されたフィーチャーフラグの値を保存するために使用するデフォルトのストレージメカニズムを指定できます。
Pennantは、array
ドライバーを介してメモリ内配列に解決されたフィーチャーフラグの値を保存するサポートを含んでいます。また、Pennantは、database
ドライバーを介してリレーショナルデータベースに解決されたフィーチャーフラグの値を永続的に保存することもできます。これは、Pennantが使用するデフォルトのストレージメカニズムです。
フィーチャーの定義
フィーチャーを定義するには、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の確率でランダムに割り当てられるべきです。
便利なことに、フィーチャー定義がロッタリーのみを返す場合、クロージャを完全に省略できます:
``````php
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 App\Models\User;
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),
};
}
}
クラスベースのフィーチャーのインスタンスを手動で解決したい場合は、instance
メソッドをFeature
ファサードで呼び出すことができます:
use Illuminate\Support\Facades\Feature;
$instance = Feature::instance(NewApi::class);
フィーチャークラスはコンテナを介して解決されるため、必要に応じてフィーチャークラスのコンストラクタに依存関係を注入できます。
保存されたフィーチャー名のカスタマイズ
デフォルトでは、Pennantはフィーチャークラスの完全修飾クラス名を保存します。保存されたフィーチャー名をアプリケーションの内部構造から切り離したい場合は、フィーチャークラスに$name
プロパティを指定できます。このプロパティの値は、クラス名の代わりに保存されます:
<?php
namespace App\Features;
class NewApi
{
/**
* The stored name of the feature.
*
* @var string
*/
public $name = 'new-api';
// ...
}
フィーチャーのチェック
フィーチャーがアクティブかどうかを判断するには、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);
}
// ...
}
フィーチャーはデフォルトで現在認証されているユーザーに対してチェックされますが、別のユーザーやスコープに対してフィーチャーを簡単にチェックすることもできます。これを実現するには、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']);
PennantをHTTPコンテキスト外で使用する場合、たとえば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
メソッドを使用して指定されたクロージャを流暢に実行できます。さらに、2つ目のクロージャを提供することもでき、フィーチャーが非アクティブな場合に実行されます:
<?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),
);
}
// ...
}
``````php
return Feature::unless(NewApi::class,
fn () => $this->resolveLegacyApiResponse($request),
fn () => $this->resolveNewApiResponse($request),
);
`
HasFeaturesトレイト
Pennantの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ディレクティブ
Bladeでフィーチャーをチェックするシームレスな体験を提供するために、Pennantは@feature
ディレクティブを提供します:
@feature('site-redesign')
<!-- 'site-redesign' is active -->
@else
<!-- 'site-redesign' is inactive -->
@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'));
レスポンスのカスタマイズ
リストされたフィーチャーのいずれかが非アクティブな場合にミドルウェアによって返されるレスポンスをカスタマイズしたい場合は、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);
}
);
// ...
}
フィーチャーチェックのインターセプト
時には、保存されたフィーチャーの値を取得する前にメモリ内でいくつかのチェックを行うことが有用です。フィーチャーフラグの背後に新しいAPIを開発していて、新しいAPIを無効にする能力を持ちつつ、ストレージ内の解決されたフィーチャー値を失いたくないと想像してください。新しいAPIにバグが見つかった場合、内部チームメンバーを除いてすべての人に対してそれを簡単に無効にし、バグを修正し、その後、以前にフィーチャーにアクセスできたユーザーのために新しいAPIを再度有効にすることができます。
これはclass-based featureのbefore
メソッドを使用して実現できます。存在する場合、before
メソッドは常にストレージから値を取得する前にメモリ内で実行されます。メソッドから非null
値が返されると、それはリクエストの間、フィーチャーの保存された値の代わりに使用されます:
<?php
namespace App\Features;
use App\Models\User;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Lottery;
class NewApi
{
/**
* Run an always-in-memory check before the stored value is retrieved.
*/
public function before(User $user): mixed
{
if (Config::get('features.new-api.disabled')) {
return $user->isInternalTeamMember();
}
}
/**
* 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),
};
}
}
このフィーチャーを使用して、以前にフィーチャーフラグの背後にあったフィーチャーのグローバル展開をスケジュールすることもできます:
<?php
namespace App\Features;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
class NewApi
{
/**
* Run an always-in-memory check before the stored value is retrieved.
*/
public function before(User $user): mixed
{
if (Config::get('features.new-api.disabled')) {
return $user->isInternalTeamMember();
}
if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) {
return true;
}
}
// ...
}
メモリ内キャッシュ
フィーチャーをチェックする際、Pennantは結果のメモリ内キャッシュを作成します。database
ドライバーを使用している場合、これは、単一のリクエスト内で同じフィーチャーフラグを再チェックしても追加のデータベースクエリがトリガーされないことを意味します。これにより、リクエストの間、フィーチャーが一貫した結果を持つことが保証されます。
メモリ内キャッシュを手動でフラッシュする必要がある場合は、flushCache
メソッドをFeature
ファサードで使用できます:
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
モデルを期待しています。このフィーチャーがユーザーのチームに対してアクティブかどうかを判断するには、for
ファサードで提供されるFeature
メソッドにチームを渡す必要があります:
if (Feature::for($user->team)->active('billing-v2')) {
return redirect('/billing/v2');
}
// ...
デフォルトスコープ
Pennantがフィーチャーをチェックするために使用するデフォルトスコープをカスタマイズすることも可能です。たとえば、すべてのフィーチャーが現在認証されているユーザーのチームに対してチェックされる場合、Feature::for($user->team)
を毎回呼び出す代わりに、チームをデフォルトスコープとして指定できます。通常、これはアプリケーションのサービスプロバイダーの1つで行うべきです:
<?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);
// ...
}
}
``````php
Feature::active('billing-v2');
// Is now equivalent to...
Feature::for($user->team)->active('billing-v2');
`
ヌラブルスコープ
フィーチャーをチェックする際に提供するスコープがnull
であり、フィーチャーの定義がヌラブルタイプを介して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
契約を実装することで、ストレージ用のスコープ値をフォーマットすることを許可します。
たとえば、単一のアプリケーションで2つの異なるフィーチャードライバーを使用していると想像してください: 組み込みの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はEloquentモデルに関連付けられたフィーチャーを保存する際に完全修飾クラス名を使用します。すでにEloquentモーフマップを使用している場合、Pennantがモーフマップを使用して保存されたフィーチャーをアプリケーション構造から切り離すことを選択できます。
これを実現するには、サービスプロバイダーでEloquentモーフマップを定義した後、Feature
ファサードのuseMorphMap
メソッドを呼び出します:
use Illuminate\Database\Eloquent\Relations\Relation;
use Laravel\Pennant\Feature;
Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);
Feature::useMorphMap();
リッチフィーチャー値
これまで、フィーチャーはバイナリ状態であると示されてきました。つまり、フィーチャーは「アクティブ」または「非アクティブ」のいずれかですが、Pennantはリッチな値を保存することも可能です。
たとえば、アプリケーションの「今すぐ購入」ボタンのために3つの新しい色をテストしていると想像してください。フィーチャー定義から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',
]));
``````php
$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
メソッドを呼び出すと、フィーチャーのリッチ値がオプションの2番目のクロージャに提供されます:
Feature::unless('purchase-button',
fn () => /* ... */,
fn ($color) => /* ... */,
);
複数のフィーチャーの取得
``````php
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
メソッドによって返される結果に表示されない可能性があることを意味します。
``````php
<?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();
// ...
}
}
`
``````php
Feature::all();
// [
// 'App\Features\NewApi' => true,
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// 'site-redesign' => true,
// ]
`
イagerローディング
Pennantは、単一のリクエストのすべての解決されたフィーチャーのメモリ内キャッシュを保持しますが、パフォーマンスの問題に直面する可能性があります。これを軽減するために、Pennantはフィーチャー値をイagerローディングする機能を提供します。
これを説明するために、フィーチャーがループ内でアクティブかどうかをチェックしていると想像してください:
use Laravel\Pennant\Feature;
foreach ($users as $user) {
if (Feature::for($user)->active('notifications-beta')) {
$user->notify(new RegistrationSuccess);
}
}
データベースドライバーを使用していると仮定すると、このコードはループ内の各ユーザーに対してデータベースクエリを実行します - 数百のクエリを実行する可能性があります。しかし、Pennantのload
メソッドを使用することで、ユーザーやスコープのコレクションのフィーチャー値をイagerローディングすることで、この潜在的なパフォーマンスボトルネックを取り除くことができます:
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',
]);
``````php
Feature::for($user)->loadAll();
`
値の更新
フィーチャーの値が初めて解決されると、基盤となるドライバーは結果をストレージに保存します。これは、リクエスト間でユーザーに一貫した体験を保証するためにしばしば必要です。しかし、時にはフィーチャーの保存された値を手動で更新したい場合があります。
これを実現するには、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');
``````php
Feature::activate('purchase-button', 'seafoam-green');
`
フィーチャーの保存された値を忘れさせるために、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のストレージドライバーによって保存された解決されたフィーチャー値のみを更新します。アプリケーション内のフィーチャー定義も更新する必要があります。
フィーチャーの消去
時には、ストレージからフィーチャー全体を消去することが有用です。これは、アプリケーションからフィーチャーを削除した場合や、すべてのユーザーに展開したいフィーチャーの定義を調整した場合に通常必要です。
``````php
// Purging a single feature...
Feature::purge('new-api');
// Purging multiple features...
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
テスト
フィーチャーフラグと対話するコードをテストする際、テスト内でフィーチャーフラグの返される値を制御する最も簡単な方法は、フィーチャーを再定義することです。たとえば、アプリケーションのサービスプロバイダーの1つで次のフィーチャーが定義されていると想像してください:
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;
test('it can control feature values', function () {
Feature::define('purchase-button', 'seafoam-green');
expect(Feature::value('purchase-button'))->toBe('seafoam-green');
});
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 Laravel\Pennant\Feature;
test('it can control feature values', function () {
Feature::define(NewApi::class, true);
expect(Feature::value(NewApi::class))->toBeTrue();
});
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がテスト中に使用するストアを構成するには、アプリケーションのphpunit.xml
ファイルにPENNANT_STORE
環境変数を定義します:
<?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に追加のドライバーを追加するには、extend
メソッドをFeature
ファサードで使用します。アプリケーションのサービスプロバイダーの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
{
/**
* 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'), []);
});
}
}
ドライバーが登録されると、アプリケーションのconfig/pennant.php
設定ファイルでredis
ドライバーを使用できます:
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => null,
],
// ...
],
イベント
Pennantは、アプリケーション全体でフィーチャーフラグを追跡する際に役立つさまざまなイベントを発行します。
Laravel\Pennant\Events\FeatureRetrieved
このイベントは、フィーチャーがチェックされるたびに発行されます。このイベントは、アプリケーション全体でフィーチャーフラグの使用に対してメトリックを作成および追跡するのに役立ちます。
Laravel\Pennant\Events\FeatureResolved
このイベントは、特定のスコープに対してフィーチャーの値が初めて解決されるときに発行されます。
Laravel\Pennant\Events\UnknownFeatureResolved
このイベントは、特定のスコープに対して未知のフィーチャーが初めて解決されるときに発行されます。このイベントをリッスンすることは、フィーチャーフラグを削除する意図があったが、アプリケーション全体に残された参照がある場合に役立ちます:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Laravel\Pennant\Events\UnknownFeatureResolved;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(function (UnknownFeatureResolved $event) {
Log::error("Resolving unknown feature [{$event->feature}].");
});
}
}
Laravel\Pennant\Events\DynamicallyRegisteringFeatureClass
このイベントは、リクエスト中にclass based featureが初めて動的にチェックされるときに発行されます。
Laravel\Pennant\Events\UnexpectedNullScopeEncountered
このイベントは、フィーチャー定義にnull
スコープが渡されたときに発生しますが、ヌルをサポートしていない場合。
この状況は優雅に処理され、フィーチャーはfalse
を返します。ただし、このフィーチャーのデフォルトの優雅な動作をオプトアウトしたい場合は、アプリケーションのAppServiceProvider
メソッドでこのイベントのリスナーを登録できます:
use Illuminate\Support\Facades\Log;
use Laravel\Pennant\Events\UnexpectedNullScopeEncountered;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(UnexpectedNullScopeEncountered::class, fn () => abort(500));
}
Laravel\Pennant\Events\FeatureUpdated
このイベントは、通常activate
またはdeactivate
を呼び出すことによって、スコープのフィーチャーを更新するときに発行されます。
Laravel\Pennant\Events\FeatureUpdatedForAllScopes
このイベントは、通常activateForEveryone
またはdeactivateForEveryone
を呼び出すことによって、すべてのスコープのフィーチャーを更新するときに発行されます。
Laravel\Pennant\Events\FeatureDeleted
このイベントは、通常forget
を呼び出すことによって、スコープのフィーチャーを削除するときに発行されます。
Laravel\Pennant\Events\FeaturesPurged
このイベントは、特定のフィーチャーを消去するときに発行されます。
Laravel\Pennant\Events\AllFeaturesPurged
このイベントは、すべてのフィーチャーを消去するときに発行されます。