Readouble

Laravel 10.x Laravel Pennant

イントロダクションIntroduction

Laravel Pennant(ペナント:三角旗)は無駄がない、シンプルで軽量な機能フラグパッケージです。機能フラグを使うことで、新しいアプリケーションの機能を躊躇なく段階的にロールアウトしたり、新しいインターフェイスデザインをA/Bテストしたり、トランクベースの開発戦略を推奨したり、その他多くのことができるようになります。Laravel Pennant[https://github.com/laravel/pennant] is a simple and light-weight feature flag package - without the cruft. Feature flags enable you to incrementally roll out new application features with confidence, A/B test new interface designs, complement a trunk-based development strategy, and much more.

インストールInstallation

まず、Composerパッケージマネージャを使って、プロジェクトにPennantをインストールします。First, install Pennant into your project using the Composer package manager:

composer require laravel/pennant

次に、vendor:publish Artisanコマンドを使用し、Pennantの設定ファイルとマイグレーションファイルをリソース公開する必要があります。Next, you should publish the Pennant configuration and migration files using the vendor:publish Artisan command:

php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"

最後に、アプリケーションのデータベースマイグレーションを実行してください。これにより、Pennantがdatabaseドライバを動かすために使う、featuresテーブルが作成されます。Finally, you should run your application's database migrations. This will create a features table that Pennant uses to power its database driver:

php artisan migrate

設定Configuration

Pennantのリソースを公開すると、その設定ファイルをconfig/pennant.phpへ保存します。この設定ファイルでPennantがデフォルトとして使用する、算出済みの機能フラグ値を保存するストレージメカニズムを指定します。After publishing Pennant's assets, its configuration file will be located at config/pennant.php. This configuration file allows you to specify the default storage mechanism that will be used by Pennant to store resolved feature flag values.

Pennantは、算出済み機能フラグの値をメモリ内の配列へ格納する、arrayドライバをサポートしています。もしくは、算出済み機能フラグ値を、リレーショナルデータベースに永続的に保存する、databaseドライバも使用できます。(これはPennantで使用する、デフォルト保存メカニズムです。)Pennant includes support for storing resolved feature flag values in an in-memory array via the array driver. Or, Pennant can store resolved feature flag values persistently in a relational database via the database driver, which is the default storage mechanism used by Pennant.

機能の定義Defining Features

機能を定義するには、Featureファサードが提供する、defineメソッドを使用します。機能の名前と、その機能の初期値を決定するため呼び出す、クロージャを指定する必要があります。To define a feature, you may use the define method offered by the Feature facade. You will need to provide a name for the feature, as well as a closure that will be invoked to resolve the feature's initial value.

通常、機能はFeatureファサードを使用し、サービスプロバイダで定義します。クロージャは、機能チェックのための「スコープ」を引数に取ります。最も一般的なのは、現在認証しているユーザーをスコープにすることでしょう。この例では、アプリケーションのユーザーへ、新しいAPIを段階的に提供する機能を定義しています。Typically, features are defined in a service provider using the Feature facade. The closure will receive the "scope" for the feature check. Most commonly, the scope is the currently authenticated user. In this example, we will define a feature for incrementally rolling out a new API to our application's users:

<?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),
        });
    }
}

ご覧の通り、この機能では以下のようなルールを設けています。As you can see, we have the following rules for our feature:

  • チーム内メンバーは全員、新しいAPIを使用できる。All internal team members should be using the new API.
  • トラフィック量が多い顧客は、新しいAPIを使用できない。Any high traffic customers should not be using the new API.
  • それ以外の場合、1/100の確率で、ランダムに機能をアクティブにする。Otherwise, the feature should be randomly assigned to users with a 1 in 100 chance of being active.

最初にnew-api機能を指定したユーザーに対してチェックしたら、クロージャの実行結果をストレージドライバへ保存します。次回、同じユーザーに対しこの機能をチェックするとき、値はストレージから取り出し、クロージャを呼び出しません。The first time the new-api feature is checked for a given user, the result of the closure will be stored by the storage driver. The next time the feature is checked against the same user, the value will be retrieved from storage and the closure will not be invoked.

使いやすいように、機能定義が抽選(lottery)を返すだけの場合は、クロージャを完全に省略できます。For convenience, if a feature definition only returns a lottery, you may omit the closure completely:

Feature::define('site-redesign', Lottery::odds(1, 1000));

クラスベースの機能Class Based Features

Pennantでは、クラスベースで機能を定義することもできます。クロージャベースの機能定義とは異なり、クラスベースの機能は、サービスプロバイダに登録する必要がありません。クラスベースの機能を生成するには、pennant:feature Artisanコマンドを実行します。デフォルトで、機能クラスはアプリケーションのapp/Featuresディレクトリへ配置します。Pennant also allows you to define class based features. Unlike closure based feature definitions, there is no need to register a class based feature in a service provider. To create a class based feature, you may invoke the pennant:feature Artisan command. By default the feature class will be placed in your application's app/Features directory:

php artisan pennant:feature NewApi

機能クラスを書く場合、resolveメソッドのみ定義する必要があります。このメソッドは、 指定したスコープに対する機能の初期値を解決するために呼び出されます。この場合も、スコープは通常、現在認証しているユーザーでしょう。When writing a feature class, you only need to define a resolve method, which will be invoked to resolve the feature's initial value for a given scope. Again, the scope will typically be the currently authenticated user:

<?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),
        };
    }
}

lightbulb Note: Feature classes are resolved via the container, so you may inject dependencies into the feature class's constructor when needed.[!NOTE] Feature classes are resolved via the container[/docs/{{version}}/container], so you may inject dependencies into the feature class's constructor when needed.

機能の保存名のカスタマイズCustomizing the Stored Feature Name

デフォルトで、Pennantは機能クラスの完全修飾クラス名を保存します。保存する機能名をアプリケーションの内部構造から切り離したい場合は、機能クラスで$nameプロパティを指定してください。このプロパティの値をクラス名の代わりに格納します。By default, Pennant will store the feature class's fully qualified class name. If you would like to decouple the stored feature name from the application's internal structure, you may specify a $name property on the feature class. The value of this property will be stored in place of the class name:

<?php

namespace App\Features;

class NewApi
{
    /**
     * 機能の保存名
     *
     * @var string
     */
    public $name = 'new-api';

    // ...
}

機能のチェックChecking Features

ある機能がアクティブであるかを判断するには、Featureファサードのactiveメソッドを使用します。デフォルトで機能は、現在認証しているユーザーを対象にチェックします。To determine if a feature is active, you may use the active method on the Feature facade. By default, features are checked against the currently authenticated user:

<?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ファサードのforメソッドを使用します。Although features are checked against the currently authenticated user by default, you may easily check the feature against another user or scope[#scope]. To accomplish this, use the for method offered by the Feature facade:

return Feature::for($user)->active('new-api')
        ? $this->resolveNewApiResponse($request)
        : $this->resolveLegacyApiResponse($request);

Pennantはさらに、機能がアクティブかを判断するのに役立つ、便利なメソッドをいくつか用意しています。Pennant also offers some additional convenience methods that may prove useful when determining if a feature is active or not:

// 指定機能がすべてアクティブであることを判断
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']);

lightbulb Note: PennantをHTTPコンテキスト外で使う場合、例えばArtisanコマンドや、キュー投入したジョブでは、機能のスコープを通常明示的に指定する必要があります。あるいは、認証済みHTTPコンテキストと、認証されていないコンテキストの両方を考慮した、デフォルトスコープを定義することもできます。[!NOTE]
When using Pennant outside of an HTTP context, such as in an Artisan command or a queued job, you should typically explicitly specify the feature's scope[#specifying-the-scope]. Alternatively, you may define a default scope[#default-scope] that accounts for both authenticated HTTP contexts and unauthenticated contexts.

クラスベース機能のチェックChecking Class Based Features

クラスベースの機能の場合、機能をチェックするときにクラス名を指定する必要があります。For class based features, you should provide the class name when checking the feature:

<?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);
    }

    // ...
}

条件付き実行Conditional Execution

whenメソッドは、機能がアクティブなときに、スムーズに指定クロージャを実行するために使います。また、2つ目のクロージャを指定し、機能が非アクティブの場合に実行させることもできます。The when method may be used to fluently execute a given closure if a feature is active. Additionally, a second closure may be provided and will be executed if the feature is inactive:

<?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メソッドと逆の働きをし、その機能が非アクティブな場合、最初のクロージャを実行します。The unless method serves as the inverse of the when method, executing the first closure if the feature is inactive:

return Feature::unless(NewApi::class,
    fn () => $this->resolveLegacyApiResponse($request),
    fn () => $this->resolveNewApiResponse($request),
);

HasFeaturesトレイトThe HasFeatures Trait

PennantのHasFeaturesトレイトは、アプリケーションのUserモデル(あるいは、機能を持つ他のモデル)に追加し、モデルから直接機能をスムーズにチェックする、便利な手法を提供しています。Pennant's HasFeatures trait may be added to your application's User model (or any other model that has features) to provide a fluent, convenient way to check features directly from the model:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Pennant\Concerns\HasFeatures;

class User extends Authenticatable
{
    use HasFeatures;

    // ...
}

このトレイトをモデルへ追加すれば、featuresメソッドを呼び出すことで、簡単に機能を確認できます。Once the trait has been added to your model, you may easily check features by invoking the features method:

if ($user->features()->active('new-api')) {
    // ...
}

もちろん、featuresメソッドは、機能を操作する他の多くの便利なメソッドへのアクセスも提供します。Of course, the features method provides access to many other convenient methods for interacting with 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 Directive

Blade内でも機能をシームレスにチェックするため、Pennantは@featureディレクティブを提供します。To make checking features in Blade a seamless experience, Pennant offers a @feature directive:

@feature('site-redesign')
    <!-- 'site-redesign'はアクティブ -->
@else
    <!-- 'site-redesign'は非アクティブ -->
@endfeature

ミドルウェアMiddleware

Pennantは、ミドルウェアも用意しています。このミドルウェアは、ルートが呼び出される前に、現在認証されているユーザーがその機能にアクセスできることを確認するために使います。ミドルウェアをルートに割り当て、ルートにアクセスするために必要な機能を指定ができます。指定した機能のどれかが、現在認証されているユーザーにとって無効である場合、ルートは400 Bad Request HTTPレスポンスを返します。静的メソッドusingには複数の機能を渡せます。Pennant also includes a middleware[/docs/{{version}}/middleware] that may be used to verify the currently authenticated user has access to a feature before a route is even invoked. You may assign the middleware to a route and specify the features that are required to access the route. If any of the specified features are inactive for the currently authenticated user, a 400 Bad Request HTTP response will be returned by the route. Multiple features may be passed to the static using method.

use Illuminate\Support\Facades\Route;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;

Route::get('/api/servers', function () {
    // ...
})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api'));

レスポンスのカスタマイズCustomizing the Response

リスト中の機能が非アクティブのときにミドルウェアが返すレスポンスをカスタマイズしたい場合は、EnsureFeaturesAreActiveミドルウェアが提供する、whenInactiveメソッドを利用してください。通常、このメソッドはアプリケーションのサービスプロバイダのbootメソッド内で呼び出す必要があります。If you would like to customize the response that is returned by the middleware when one of the listed features is inactive, you may use the whenInactive method provided by the EnsureFeaturesAreActive middleware. Typically, this method should be invoked within the boot method of one of your application's service providers:

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);
        }
    );

    // ...
}

メモリ内キャッシュIn-Memory Cache

機能をチェックすると、Pennantはその結果のメモリ内キャッシュを作成します。databaseドライバを使っている場合、これは同じ機能フラグを一つのリクエストで再チェックしても、追加のデータベースクエリが発生しないことを意味します。これはまた、その機能がリクエストの間、一貫した結果を持つことを保証します。When checking a feature, Pennant will create an in-memory cache of the result. If you are using the database driver, this means that re-checking the same feature flag within a single request will not trigger additional database queries. This also ensures that the feature has a consistent result for the duration of the request.

メモリ内のキャッシュを手作業で消去する必要がある場合は、Featureファサードが提供するflushCacheメソッドを使用してください。If you need to manually flush the in-memory cache, you may use the flushCache method offered by the Feature facade:

Feature::flushCache();

スコープScope

スコープの指定Specifying the Scope

説明してきたように、機能は通常、現在認証しているユーザーに対してチェックされます。しかし、これは必ずしもあなたのニーズに合うとは限りません。そのため、Featureファサードのforメソッドでは、ある機能をチェックする対象のスコープを指定できます。As discussed, features are typically checked against the currently authenticated user. However, this may not always suit your needs. Therefore, it is possible to specify the scope you would like to check a given feature against via the Feature facade's for method:

return Feature::for($user)->active('new-api')
        ? $this->resolveNewApiResponse($request)
        : $this->resolveLegacyApiResponse($request);

もちろん、機能のスコープは、「ユーザー」に限定されません。新しい課金システムを構築しており、個々のユーザーではなく、チーム全体にロールアウトしていると想像してみてください。たぶん、古いチームには、新しいチームよりもゆっくりとロールアウトしたいと思うでしょう。この機能解決のクロージャは、次のようなものになります。Of course, feature scopes are not limited to "users". Imagine you have built a new billing experience that you are rolling out to entire teams rather than individual users. Perhaps you would like the oldest teams to have a slower rollout than the newer teams. Your feature resolution closure might look something like the following:

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メソッドへ、チームを渡す必要があります。You will notice that the closure we have defined is not expecting a User, but is instead expecting a Team model. To determine if this feature is active for a user's team, you should pass the team to the for method offered by the Feature facade:

if (Feature::for($user->team)->active('billing-v2')) {
    return redirect()->to('/billing/v2');
}

// ...

デフォルトスコープDefault Scope

Pennant が機能をチェックするのに使うデフォルトのスコープをカスタマイズすることも可能です。たとえば、すべての機能をユーザーではなく、現在認証しているユーザーのチームに対してチェックするとします。機能をチェックするたびに、Feature::for($user->team)を毎回呼び出す代わりに、チームをデフォルトのスコープとして指定できます。一般に、これはアプリケーションのいずれかのサービスプロバイダで行う必要があります。It is also possible to customize the default scope Pennant uses to check features. For example, maybe all of your features are checked against the currently authenticated user's team instead of the user. Instead of having to call Feature::for($user->team) every time you check a feature, you may instead specify the team as the default scope. Typically, this should be done in one of your application's service providers:

<?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メソッドで明示的にスコープを指定しなかった場合、機能チェックでは現在認証しているユーザーのチームをデフォルトのスコープとして使用するようになりました。If no scope is explicitly provided via the for method, the feature check will now use the currently authenticated user's team as the default scope:

Feature::active('billing-v2');

// 上記コードが以下と同じ動作をするようになった

Feature::for($user->team)->active('billing-v2');

NULL許可のスコープNullable Scope

もし、ある機能をチェックするときに指定したスコープで、nullable型またはnullを含むunion型により、機能の定義でnullをサポートしていない場合、Pennantは自動的にその機能の結果値としてfalseを返します。If the scope you provide when checking a feature is null and the feature's definition does not support null via a nullable type or by including null in a union type, Pennant will automatically return false as the feature's result value.

したがって、機能に渡すスコープがnullになる可能性があり、その機能の値リゾルバを呼び出したい場合は、機能の定義でこれを考慮する必要があります。nullスコープは、Artisan コマンド、キュー投入したジョブ、もしくは未認証のルート内で機能をチェックする場合に発生する可能性があります。これらのコンテキストでは通常、認証済みユーザーが存在しないため、デフォルトのスコープは null になります。So, if the scope you are passing to a feature is potentially null and you want the feature's value resolver to be invoked, you should account for that in your feature's definition. A null scope may occur if you check a feature within an Artisan command, queued job, or unauthenticated route. Since there is usually not an authenticated user in these contexts, the default scope will be null.

もし、常に機能のスコープを明示的に指定しないのであれば、スコープのタイプを"nullable"にし、機能定義のロジック内でnull スコープ値を確実に処理してください。If you do not always explicitly specify your feature scope[#specifying-the-scope] then you should ensure the scope's type is "nullable" and handle the null scope value within your feature definition logic:

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),
});

スコープの識別子Identifying Scope

Pennant我用意しているarraydatabaseストレージドライバは、すべてのPHPデータ型とEloquentモデルのスコープ識別子を適切に保存する方法を知っています。しかし、サードパーティのPennantドライバを使用する場合、そのドライバはEloquentモデルやその他のカスタム型に対する識別子を正しく格納する方法を知らない可能性があります。Pennant's built-in array and database storage drivers know how to properly store scope identifiers for all PHP data types as well as Eloquent models. However, if your application utilizes a third-party Pennant driver, that driver may not know how to properly store an identifier for an Eloquent model or other custom types in your application.

こうした観点から、Pennantは、アプリケーションの中でPennantのスコープとして使用するオブジェクトへ、FeatureScopeable契約を実装することにより、スコープの値を保存用にフォーマットできるようにしました。In light of this, Pennant allows you to format scope values for storage by implementing the FeatureScopeable contract on the objects in your application that are used as Pennant scopes.

例えば、1つのアプリケーションで2つの異なる機能ドライバを使用しているとします。組み込みのdatabaseドライバと、サードパーティの"Flag Rocket"ドライバです。"Flag Rocket"ドライバはEloquentモデルを適切に保存する方法を知りません。代わりに、FlagRocketUserインスタンスを必要とします。FeatureScopeableが定義するtoFeatureIdentifierを実装し、アプリケーションで使用する各ドライバに提供する保存可能なスコープの値をカスタマイズできます。For example, imagine you are using two different feature drivers in a single application: the built-in database driver and a third-party "Flag Rocket" driver. The "Flag Rocket" driver does not know how to properly store an Eloquent model. Instead, it requires a FlagRocketUser instance. By implementing the toFeatureIdentifier defined by the FeatureScopeable contract, we can customize the storable scope value provided to each driver used by our application:

<?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),
        };
    }
}

スコープのシリアライズSerializing Scope

PennantはEloquentモデルに関連付けた機能を格納するとき、デフォルトで完全修飾クラス名を使います。Eloquentモーフィックマップを使っている場合は、Pennantでもモーフィックマップを使い、保存した機能をアプリケーションの構造から切り離せます。By default, Pennant will use a fully qualified class name when storing a feature associated with an Eloquent model. If you are already using an Eloquent morph map[/docs/{{version}}/eloquent-relationships#custom-polymorphic-types], you may choose to have Pennant also use the morph map to decouple the stored feature from your application structure.

これを行なうには、サービスプロバイダでEloquentモーフマップを定義した後に、FeatureファサードのuseMorphMapメソッドを呼び出します。To achieve this, after defining your Eloquent morph map in a service provider, you may invoke the Feature facade's useMorphMap method:

use Illuminate\Database\Eloquent\Relations\Relation;
use Laravel\Pennant\Feature;

Relation::enforceMorphMap([
    'post' => 'App\Models\Post',
    'video' => 'App\Models\Video',
]);

Feature::useMorphMap();

機能のリッチな値Rich Feature Values

ここまで、機能は主にバイナリ状態、つまり「アクティブ」か「非アクティブ」かで取り扱ってきましたが、Pennantはリッチな値も格納できます。Until now, we have primarily shown features as being in a binary state, meaning they are either "active" or "inactive", but Pennant also allows you to store rich values as well.

例えば、アプリケーションの「Buy now」ボタンに3つの新しい色をテストする場合を考えてみましょう。機能定義からtruefalseを返す代わりに、文字列を返せます。For example, imagine you are testing three new colors for the "Buy now" button of your application. Instead of returning true or false from the feature definition, you may instead return a string:

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機能の値を取得できます。You may retrieve the value of the purchase-button feature using the value method:

$color = Feature::value('purchase-button');

また、Pennantが用意しているBladeディレクティブでは簡単に、機能の現在の値に基づいて、条件付きでコンテンツをレンダできます。Pennant's included Blade directive also makes it easy to conditionally render content based on the current value of the feature:

@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

lightbulb Note: When using rich values, it is important to know that a feature is considered "active" when it has any value other than false.[!NOTE] When using rich values, it is important to know that a feature is considered "active" when it has any value other than false.

条件付きwhenメソッドを呼び出すと、その機能のリッチな値が最初のクロージャに提供されます。When calling the conditional when[#conditional-execution] method, the feature's rich value will be provided to the first closure:

Feature::when('purchase-button',
    fn ($color) => /* ... */,
    fn () => /* ... */,
);

同様に、条件付きのunlessメソッドを呼び出すと、その機能のリッチな値がオプションの2番目のクロージャへ渡されます。Likewise, when calling the conditional unless method, the feature's rich value will be provided to the optional second closure:

Feature::unless('purchase-button',
    fn () => /* ... */,
    fn ($color) => /* ... */,
);

複数の機能の取得Retrieving Multiple Features

valueメソッドで、指定スコープに対する複数の機能を取得できます。The values method allows the retrieval of multiple features for a given scope:

Feature::values(['billing-v2', 'purchase-button']);

// [
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
// ]

もしくは、allメソッドを使用し、指定スコープに定義されているすべての機能の値を取得することもできます。Or, you may use the all method to retrieve the values of all defined features for a given scope:

Feature::all();

// [
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
//     'site-redesign' => true,
// ]

しかし、クラスベースの機能は動的に登録されますので、明示的にチェックするまでPennantにはわかりません。つまり、アプリケーションのクラスベースの機能は、現在のリクエストでチェックしていない場合、allメソッドが返す結果に現れない可能性があります。However, class based features are dynamically registered and are not known by Pennant until they are explicitly checked. This means your application's class based features may not appear in the results returned by the all method if they have not already been checked during the current request.

もし、 all メソッドを使うときに、特徴クラスが常に含まれるよう確実にしたい場合は、Pennantの機能発見機構を使ってください。最初に、アプリケーションのサービスプロバイダの一つの中で、discoverメソッドを呼び出します。If you would like to ensure that feature classes are always included when using the all method, you may use Pennant's feature discovery capabilities. To get started, invoke the discover method in one of your application's service providers:

<?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メソッドは、現在のリクエストでチェック済みかに関係なく、これらのクラスを結果に含めます。The discover method will register all of the feature classes in your application's app/Features directory. The all method will now include these classes in its results, regardless of whether they have been checked during the current request:

Feature::all();

// [
//     'App\Features\NewApi' => true,
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
//     'site-redesign' => true,
// ]

EagerロードEager Loading

Pennantは、一つのリクエストで解決したすべての機能のメモリ内キャッシュを保持しますが、それでも性能上の問題が発生する可能性があります。これを軽減するために、Pennantは機能の値をEagerロードする機能を提供しています。Although Pennant keeps an in-memory cache of all resolved features for a single request, it is still possible to encounter performance issues. To alleviate this, Pennant offers the ability to eager load feature values.

これを理解するため、機能がアクティブであるかをループの中でチェックしていると考えてください。To illustrate this, imagine that we are checking if a feature is active within a loop:

use Laravel\Pennant\Feature;

foreach ($users as $user) {
    if (Feature::for($user)->active('notifications-beta')) {
        $user->notify(new RegistrationSuccess);
    }
}

データベースドライバを使用していると仮定すると、このコードはループ内のすべてのユーザーに対してデータベースクエリを実行することになり、潜在的に数百のクエリを実行することになります。しかし、Pennantのloadメソッドを使えば、ユーザーやスコープのコレクションの値をEagerロードでき、この潜在的なパフォーマンスのボトルネックを取り除けます。Assuming we are using the database driver, this code will execute a database query for every user in the loop - executing potentially hundreds of queries. However, using Pennant's load method, we can remove this potential performance bottleneck by eager loading the feature values for a collection of users or scopes:

Feature::for($users)->load(['notifications-beta']);

foreach ($users as $user) {
    if (Feature::for($user)->active('notifications-beta')) {
        $user->notify(new RegistrationSuccess);
    }
}

機能の値がまだロードされていないときだけロードするには、loadMissingメソッドを使用します。To load feature values only when they have not already been loaded, you may use the loadMissing method:

Feature::for($users)->loadMissing([
    'new-api',
    'purchase-button',
    'notifications-beta',
]);

値の更新Updating Values

機能の値を初めて解決するとき、裏で動作しているドライバはその結果をストレージへ保存します。これは、リクエスト間で一貫したユーザー体験を保証するために、多くの場合必要です。しかし、時には、保存している機能の値を手作業で更新したい場合もあるでしょう。When a feature's value is resolved for the first time, the underlying driver will store the result in storage. This is often necessary to ensure a consistent experience for your users across requests. However, at times, you may want to manually update the feature's stored value.

このために、activatedeactivateメソッドを使用して、機能の"on"と"off"を切り替えます。To accomplish this, you may use the activate and deactivate methods to toggle a feature "on" or "off":

use Laravel\Pennant\Feature;

// デフォルトのスコープの機能を有効にする
Feature::activate('new-api');

// 指定のスコープの機能を無効にする
Feature::for($user->team)->deactivate('billing-v2');

または、activateメソッドへ第2引数を指定し、手作業で機能へリッチな値を設定することも可能です。It is also possible to manually set a rich value for a feature by providing a second argument to the activate method:

Feature::activate('purchase-button', 'seafoam-green');

Pennantへ、ある機能の保存値を消去するように指示するには、forgetメソッドを使用します。その機能を再びチェックするとき、Pennantはその機能の定義により、値を解決します。To instruct Pennant to forget the stored value for a feature, you may use the forget method. When the feature is checked again, Pennant will resolve the feature's value from its feature definition:

Feature::forget('purchase-button');

バルク更新Bulk Updates

保存している機能の値を一括で更新するには、activateForEveryoneメソッドとdeactivateForEveryoneメソッドを使用します。To update stored feature values in bulk, you may use the activateForEveryone and deactivateForEveryone methods.

例えば、あなたがnew-api機能の安定性に自信を持ち、チェックアウトフローに最適な'purchase-button'の色を見つけたとします。それに応じて、全ユーザーの機能値を更新できます。For example, imagine you are now confident in the new-api feature's stability and have landed on the best 'purchase-button' color for your checkout flow - you can update the stored value for all users accordingly:

use Laravel\Pennant\Feature;

Feature::activateForEveryone('new-api');

Feature::activateForEveryone('purchase-button', 'seafoam-green');

もしくは、全ユーザーに対し、その機能を非アクティブにすることもできます。Alternatively, you may deactivate the feature for all users:

Feature::deactivateForEveryone('new-api');

lightbulb Note: This will only update the resolved feature values that have been stored by Pennant's storage driver. You will also need to update the feature definition in your application.[!NOTE] This will only update the resolved feature values that have been stored by Pennant's storage driver. You will also need to update the feature definition in your application.

機能の削除Purging Features

時には、ストレージから機能全体を取り除くのが、有用な場合があります。これは、アプリケーションからその機能を削除した場合や、その機能の定義に調整を加え、全ユーザーにロールアウトする状況で、典型的に必要になります。Sometimes, it can be useful to purge an entire feature from storage. This is typically necessary if you have removed the feature from your application or you have made adjustments to the feature's definition that you would like to rollout to all users.

ある機能に対して保存されているすべての値を削除するには、purgeメソッドを使用します。You may remove all stored values for a feature using the purge method:

// 1機能の削除
Feature::purge('new-api');

// 複数機能の削除
Feature::purge(['new-api', 'purchase-button']);

もしストレージから、機能を削除したい場合は、引数なしでpurgeメソッドを呼び出します。If you would like to purge all features from storage, you may invoke the purge method without any arguments:

Feature::purge();

アプリケーションのデプロイパイプラインの一貫として、機能を削除できると便利であるため、Pennantは指定した機能をストレージから削除する、pennant:purge Artisanコマンドを用意しています。As it can be useful to purge features as part of your application's deployment pipeline, Pennant includes a pennant:purge Artisan command which will purge the provided features from storage:

php artisan pennant:purge new-api

php artisan pennant:purge new-api purchase-button

また、リスト内で指定した機能を除くすべての機能を削除することも可能です。例えば、"new-api"と"purchase-button"機能を保存したまま、他のすべてのフィーチャーを削除したいとします。これを行うには、--exceptオプションへこれらの機能名を渡します。It is also possible to purge all features except those in a given feature list. For example, imagine you wanted to purge all features but keep the values for the "new-api" and "purchase-button" features in storage. To accomplish this, you can pass those feature names to the --except option:

php artisan pennant:purge --except=new-api --except=purchase-button

使いやすいように、pennant:purgeコマンドは、--except-registeredフラグもサポートしています。このフラグは、サービスプロバイダで明示的に登録している機能以外のすべての機能を削除することを意味します。For convenience, the pennant:purge command also supports an --except-registered flag. This flag indicates that all features except those explicitly registered in a service provider should be purged:

php artisan pennant:purge --except-registered

テストTesting

機能フラグを操作するコードをテストする場合、機能フラグの返り値をテストでコントロールする最も簡単な方法は、その機能を単純に再定義することです。たとえば、アプリケーションのサービスプロバイダに次のような機能が定義されているとしましょう。When testing code that interacts with feature flags, the easiest way to control the feature flag's returned value in your tests is to simply re-define the feature. For example, imagine you have the following feature defined in one of your application's service provider:

use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;

Feature::define('purchase-button', fn () => Arr::random([
    'blue-sapphire',
    'seafoam-green',
    'tart-orange',
]));

テストの中で、機能の戻り値を変更するには、テストの最初にその機能を再定義します。以下のテストは、サービスプロバイダにArr::random()の実装が残っていても、常にパスします。To modify the feature's returned value in your tests, you may re-define the feature at the beginning of the test. The following test will always pass, even though the Arr::random() implementation is still present in the service provider:

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'));
}

クラスベースの機能でも、同様のアプローチが可能です。The same approach may be used for class based features:

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インスタンスを返すのであれば、便利な利用できるテストヘルパがあります。If your feature is returning a Lottery instance, there are a handful of useful testing helpers available[/docs/{{version}}/helpers#testing-lotteries].

保存域の設定Store Configuration

アプリケーションのphpunit.xmlファイルで、PENNANT_STORE環境変数を定義すれば、テスト中にPennantが使用する保存域を設定できます。You may configure the store that Pennant will use during testing by defining the PENNANT_STORE environment variable in your application's phpunit.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
    <!-- ... -->
    <php>
        <env name="PENNANT_STORE" value="array"/>
        <!-- ... -->
    </php>
</phpunit>

カスタム機能ドライバの追加Adding Custom Pennant Drivers

ドライバの実装Implementing the Driver

もし、Pennantの既存ストレージドライバが、どれもあなたのアプリケーションのニーズに合わない場合は、独自のストレージドライバを書いてください。カスタムドライバは、Laravel\Pennant\Contracts\Driverインターフェイスを実装する必要があります。If none of Pennant's existing storage drivers fit your application's needs, you may write your own storage driver. Your custom driver should implement the Laravel\Pennant\Contracts\Driver interface:

<?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を見てください。Now, we just need to implement each of these methods using a Redis connection. For an example of how to implement each of these methods, take a look at the Laravel\Pennant\Drivers\DatabaseDriver in the Pennant source code[https://github.com/laravel/pennant/blob/1.x/src/Drivers/DatabaseDriver.php]

lightbulb Note: Laravelは、拡張機能を格納するディレクトリを用意していません。好きな場所に自由に配置できます。この例では、RedisFeatureDriverを格納するために、Extensionsディレクトリを作成しました。[!NOTE]
Laravel does not ship with a directory to contain your extensions. You are free to place them anywhere you like. In this example, we have created an Extensions directory to house the RedisFeatureDriver.

ドライバの登録Registering the Driver

ドライバを実装したら、Laravelに登録します。Pennantへドライバを追加するには、Featureファサードが提供するextendメソッドをしようします。extendメソッドは、アプリケーションの[サービスプロバイダ](/docs/{{version}}/providers のbootメソッドから呼び出す必要があります。Once your driver has been implemented, you are ready to register it with Laravel. To add additional drivers to Pennant, you may use the extend method provided by the Feature facade. You should call the extend method from the boot method of one of your application's service provider[/docs/{{version}}/providers]:

<?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ドライバが利用できるようになります。Once the driver has been registered, you may use the redis driver in your application's config/pennant.php configuration file:

'stores' => [

    'redis' => [
        'driver' => 'redis',
        'connection' => null,
    ],

    // ...

],

イベントEvents

Pennantは、アプリケーション全体の機能フラグを追跡するときに便利な、さまざまなイベントを発行します。Pennant dispatches a variety of events that can be useful when tracking feature flags throughout your application.

Laravel\Pennant\Events\RetrievingKnownFeatureLaravel\Pennant\Events\RetrievingKnownFeature

このイベントは、特定のスコープに対するリクエスト中に、既知の機能を初めて取得したときに発行します。このイベントは、アプリケーション全体で使用する機能フラグに対するメトリックを作成し、追跡するのに便利です。This event is dispatched the first time a known feature is retrieved during a request for a specific scope. This event can be useful to create and track metrics against the feature flags that are being used throughout your application.

Laravel\Pennant\Events\RetrievingUnknownFeatureLaravel\Pennant\Events\RetrievingUnknownFeature

このイベントは、特定のスコープへのリクエスト中に、未知の機能を初めて取得したときに発行します。このイベントは、ある機能フラグを削除するつもりが、誤ってアプリケーション全体にそのフラグへの参照を残してしまった場合に便利です。This event is dispatched the first time an unknown feature is retrieved during a request for a specific scope. This event can be useful if you have intended to remove a feature flag, but may have accidentally left some stray references to it throughout your application.

例えば、このイベントをリッスンして、それが発生したときに、reportや例外を投げるのが便利でしょう。For example, you may find it useful to listen for this event and report or throw an exception when it occurs:

<?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\DynamicallyDefiningFeatureLaravel\Pennant\Events\DynamicallyDefiningFeature

このイベントは、クラスベースの機能をリクエスト中に、初めて動的にチェックするときに発行します。This event is dispatched when a class based feature is being dynamically checked for the first time during a request.

章選択

設定

明暗テーマ
light_mode
dark_mode
brightness_auto システム設定に合わせる
テーマ選択
photo_size_select_actual デフォルト
photo_size_select_actual モノクローム(白黒)
photo_size_select_actual Solarized風
photo_size_select_actual GitHub風(青ベース)
photo_size_select_actual Viva(黄緑ベース)
photo_size_select_actual Happy(紫ベース)
photo_size_select_actual Mint(緑ベース)
コードハイライトテーマ選択

明暗テーマごとに、コードハイライトのテーマを指定できます。

テーマ配色確認
スクリーン表示幅
640px
80%
90%
100%

768px以上の幅があるときのドキュメント部分表示幅です。

インデント
無し
1rem
2rem
3rem
原文確認
原文を全行表示
原文を一行ずつ表示
使用しない

※ 段落末のEボタンへカーソルオンで原文をPopupします。

Diff表示形式
色分けのみで区別
行頭の±で区別
削除線と追記で区別

※ [tl!…]形式の挿入削除行の表示形式です。

Pagination和文
ペジネーション
ペギネーション
ページネーション
ページ付け
Scaffold和文
スカフォールド
スキャフォールド
型枠生成
本文フォント

総称名以外はCSSと同様に、"〜"でエスケープしてください。

コードフォント

総称名以外はCSSと同様に、"〜"でエスケープしてください。

保存内容リセット

localStrageに保存してある設定項目をすべて削除し、デフォルト状態へ戻します。

ヘッダー項目移動

キーボード操作