1. Пакеты
  2. Laravel Pennant

Присоединяйся к нашему Telegram сообществу @webblend!

Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.

Введение

Laravel Pennant - это простой и легкий пакет флажков функций без лишних элементов. Флажки функций позволяют вам поэтапно внедрять новые функции приложения с уверенностью, проводить A/B-тестирование новых дизайнов интерфейса, дополнять стратегию разработки на основе ствола и многое другое.

Установка

Сначала установите Pennant в свой проект, используя менеджер пакетов Composer:

composer require laravel/pennant

Затем вы должны опубликовать файлы конфигурации и миграции Pennant с использованием команды Artisan vendor:publish:

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

Наконец, вы должны выполнить миграции базы данных вашего приложения. Это создаст таблицу features, которую Pennant использует для обеспечения функции своим драйвером database:

php artisan migrate

Настройка

После публикации ресурсов 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
{
/**
* Запускает все службы приложения.
*/
public function boot(): void
{
Feature::define('new-api', fn (User $user) => match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
});
}
}

Как видите, у нас есть следующие правила для нашей функции:

  • Все внутренние члены команды должны использовать новый API.
  • Ни один из клиентов с высоким трафиком не должен использовать новый API.
  • В противном случае функция должна быть случайным образом назначена пользователям с вероятностью активации 1 к 100.

Первый раз, когда для данного пользователя проверяется функция new-api, результат замыкания будет сохранен драйвером хранения. При следующей проверке функции для того же пользователя значение будет извлечено из хранилища, и замыкание вызвано не будет.

Для удобства, если определение функции возвращает только результат лотереи, вы можете полностью опустить замыкание:

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

Функции на основе класса

Pennant также позволяет вам определять функции на основе класса. В отличие от определений функций на основе замыкания, нет необходимости регистрировать функцию на основе класса в поставщике услуг. Чтобы создать функцию на основе класса, вы можете вызвать команду Artisan pennant:feature. По умолчанию класс функции будет помещен в каталог app/Features вашего приложения:

php artisan pennant:feature NewApi

При написании класса функции вам нужно определить только метод resolve, который будет вызван для разрешения начального значения функции для заданной области. Опять же, область обычно будет в настоящее время аутентифицированным пользователем:

<?php
 
namespace App\Features;
 
use Illuminate\Support\Lottery;
 
class NewApi
{
/**
* Разрешает начальное значение функции.
*/
public function resolve(User $user): mixed
{
return match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
};
}
}

Note Feature classes are resolved via the container, so you may inject dependencies into the feature class's constructor when needed.

Проверка функций

Чтобы определить, активна ли функция, вы можете использовать метод active фасада Feature. По умолчанию функции проверяются относительно в настоящее время аутентифицированного пользователя:

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

Хотя по умолчанию функции проверяются относительно в настоящее время аутентифицированного пользователя, вы можете легко проверить функцию относительно другого пользователя или области. Для этого используйте метод for фасада Feature:

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

Pennant также предлагает несколько дополнительных удобных методов, которые могут оказаться полезными при определении активности функции или ее отсутствия:

// Определить, активны ли все предоставленные функции...
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
{
/**
* Отображает список ресурсов.
*/
public function index(Request $request): Response
{
return Feature::active(NewApi::class)
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
}
 
// ...
}

Условное выполнение

Метод when можно использовать для плавного выполнения заданного замыкания, если функция активна. Кроме того, второе замыкание может быть предоставлено и выполнено, если функция неактивна:

<?php
 
namespace App\Http\Controllers;
 
use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
 
class PodcastController
{
/**
* Отображает список ресурсов.
*/
public function index(Request $request): Response
{
return Feature::when(NewApi::class,
fn () => $this->resolveNewApiResponse($request),
fn () => $this->resolveLegacyApiResponse($request),
);
}
 
// ...
}

Метод unless служит инверсией метода when, выполняя первое замыкание, если функция неактивна:

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

Трейт HasFeatures

Трейт HasFeatures Pennant может быть добавлен в модель пользователя вашего приложения (или в любую другую модель, у которой есть функции), чтобы предоставить гибкий и удобный способ проверки функций напрямую из модели:

<?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 предоставляет доступ к многим другим удобным методам для взаимодействия с функциями:

// Значения...
$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 также включает промежуточное ПО, которое можно использовать для проверки того, что текущему аутентифицированному пользователю предоставлен доступ к функции до того, как будет вызван маршрут. Вы можете присвоить промежуточное ПО маршруту и указать функции, которые требуются для доступа к маршруту. Если какие-либо из указанных функций неактивны для текущего аутентифицированного пользователя, маршрут вернет HTTP-ответ 400 Bad Request. Несколько функций могут быть переданы методу 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;
 
/**
* Запускает все службы приложения.
*/
public function boot(): void
{
EnsureFeaturesAreActive::whenInactive(
function (Request $request, array $features) {
return new Response(status: 403);
}
);
 
// ...
}

Кэш в памяти

При проверке функции Pennant создаст кэш в памяти результата. Если вы используете драйвер database, это означает, что повторная проверка одного и того же флажка функций в рамках одного запроса не вызовет дополнительных запросов к базе данных. Это также обеспечивает согласованный результат функции на протяжении всего запроса.

Если вам нужно вручную очистить кэш в памяти, вы можете использовать метод flushCache, предоставленный фасадом Feature:

Feature::flushCache();

Область применения

Указание области применения

Как обсуждалось, функции обычно проверяются относительно в настоящее время аутентифицированного пользователя. Однако это может не всегда соответствовать вашим потребностям. Поэтому можно указать область, в которой вы хотели бы проверить данную функцию, с использованием метода for фасада Feature:

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
{
/**
* Запускает все службы приложения.
*/
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 или включением null в объединенный тип, Pennant автоматически вернет false в качестве значения результата функции.

Так что, если область, которую вы передаете функции, потенциально может быть null, и вы хотите вызвать разрешитель значения функции, вы должны учесть это в определении вашей функции. Область null может возникнуть, если вы проверяете функцию в Artisan-команде, в очередной задаче или в неаутентифицированном маршруте. Поскольку в этих контекстах обычно нет аутентифицированного пользователя, область по умолчанию будет null.

Если вы не всегда явно указываете область применения функции, то убедитесь, что тип области "nullable", и обработайте значение области 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 позволяет вам форматировать значения области для хранения, реализуя контракт 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
{
/**
* Приводит объект к идентификатору области функций для заданного драйвера.
*/
public function toFeatureIdentifier(string $driver): mixed
{
return match($driver) {
'database' => $this,
'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id),
};
}
}

Сериализация области применения

По умолчанию Pennant будет использовать полное имя класса при хранении функции, связанной с моделью Eloquent. Если вы уже используете морфную карту Eloquent, вы можете выбрать использование Pennant также морфной карты для отделения хранимой функции от структуры вашего приложения.

Для достижения этого, после определения вашей морфной карты Eloquent в поставщике службы, вы можете вызвать метод useMorphMap фасада Feature:

use Illuminate\Database\Eloquent\Relations\Relation;
use Laravel\Pennant\Feature;
 
Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);
 
Feature::useMorphMap();

Богатые значения функций

До сих пор мы в основном показывали функции как двоичное состояние, что означает, что они либо "активны", либо "неактивны", но Pennant также позволяет вам хранить также и богатые значения.

Например, представьте, что вы тестируете три новых цвета для кнопки "Купить сейчас" в вашем приложении. Вместо возврата true или false из определения функции вы можете вместо этого вернуть строку:

use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;
 
Feature::define('purchase-button', fn (User $user) => Arr::random([
'blue-sapphire',
'seafoam-green',
'tart-orange',
]));

Вы можете получить значение функции purchase-button, используя метод value:

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

Включенная директива Blade в Pennant также упрощает условное отображение контента в зависимости от текущего значения функции:

@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 When using rich values, it is important to know that a feature is considered "active" when it has any value other than false.

При вызове метода условного выполнения when богатое значение функции будет предоставлено первому замыканию:

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

Точно так же, при вызове метода условного выполнения unless богатое значение функции будет предоставлено второму необязательному замыканию:

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

Получение нескольких функций

Метод values позволяет извлекать несколько функций для заданной области:

Feature::values(['billing-v2', 'purchase-button']);
 
// [
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// ]

Или вы можете использовать метод all для извлечения значений всех определенных функций для заданной области:

Feature::all();
 
// [
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// 'site-redesign' => true,
// ]

Однако классовые функции динамически регистрируются и неизвестны Pennant до их явной проверки. Это означает, что классовые функции вашего приложения могут не появляться в результатах, возвращаемых методом all, если они не были проверены в текущем запросе.

Если вы хотите гарантировать, что классы функций всегда включены при использовании метода all, вы можете использовать возможности обнаружения функций Pennant. Для начала вызовите метод discover в одном из поставщиков услуг вашего приложения:

<?php
 
namespace App\Providers;
 
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Запускает все службы приложения.
*/
public function boot(): void
{
Feature::discover();
 
// ...
}
}

Метод discover зарегистрирует все классы функций в каталоге вашего приложения app/Features. Теперь метод all будет включать эти классы в свои результаты, независимо от того, были ли они проверены в текущем запросе:

Feature::all();
 
// [
// 'App\Features\NewApi' => true,
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// 'site-redesign' => true,
// ]

Предварительная загрузка

Хотя Pennant хранит кеш в памяти для всех разрешенных функций для одного запроса, все равно возможны проблемы с производительностью. Чтобы избежать этого, Pennant предлагает возможность предварительной загрузки значений функций.

Для иллюстрации представьте себе, что мы проверяем, активна ли функция внутри цикла:

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

Предположим, что мы используем драйвер базы данных, этот код будет выполнять запрос к базе данных для каждого пользователя в цикле - потенциально выполняя сотни запросов. Однако, используя метод load Pennant, мы можем устранить этот потенциальный узкий момент производительности, предварительно загружая значения функций для коллекции пользователей или областей:

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 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.

Очистка функций

Иногда бывает полезно очистить всю функцию из хранилища. Это обычно необходимо, если вы удалили функцию из вашего приложения или внесли изменения в определение функции, которые вы хотели бы распространить на всех пользователей.

Вы можете удалить все сохраненные значения для функции с использованием метода purge:

// Очистка одной функции...
Feature::purge('new-api');
 
// Purging multiple features...
Feature::purge(['new-api', 'purchase-button']);

Если вы хотите очистить все функции из хранилища, вы можете вызвать метод purge без аргументов:

Feature::purge();

Поскольку бывает полезно очищать функции как часть конвейера развертывания вашего приложения, Pennant включает команду Artisan pennant:purge:

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, есть несколько полезных вспомогательных средств для тестирования.

Настройка хранилища

Вы можете настроить хранилище, которое Pennant будет использовать во время тестирования, определив переменную окружения 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 в исходном коде Pennant

Примечание 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
{
/**
* Регистрирует все службы приложения.
*/
public function register(): void
{
// ...
}
 
/**
* Запускает все службы приложения.
*/
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,
],
 
// ...
 
],

События

Pennant отправляет различные события, которые могут быть полезны при отслеживании флагов функций в вашем приложении.

Laravel\Pennant\Events\RetrievingKnownFeature

Это событие отправляется в первый раз, когда известная функция извлекается во время запроса для определенной области. Это событие может быть полезным для создания и отслеживания метрик по флагам функций, используемым в вашем приложении.

Laravel\Pennant\Events\RetrievingUnknownFeature

Это событие отправляется в первый раз, когда извлекается неизвестная функция во время запроса для определенной области. Это событие может быть полезным, если вы собираетесь удалить флаг функции, но можете случайно оставить некоторые бесхозные ссылки на него в вашем приложении.

Например, вы можете найти полезным прослушивать это событие и report или вызывать исключение, когда оно происходит:

<?php
 
namespace App\Providers;
 
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use Laravel\Pennant\Events\RetrievingUnknownFeature;
 
class EventServiceProvider extends ServiceProvider
{
/**
* Регистрирует другие события для вашего приложения.
*/
public function boot(): void
{
Event::listen(function (RetrievingUnknownFeature $event) {
report("Resolving unknown feature [{$event->feature}].");
});
}
}

Laravel\Pennant\Events\DynamicallyDefiningFeature

Это событие отправляется, когда классовая функция проверяется динамически в первый раз во время запроса.