- Введение
- Установка
- Настройка
- Определение функций
- Проверка функций
- Область действия
- Расширенные значения функций
- Получение нескольких функций
- Жадная загрузка
- Обновление значений
- Тестирование
- Добавление пользовательских драйверов Pennant
- События
#Введение
Laravel Pennant — это простой и лёгкий пакет для работы с feature flags без лишнего кода. Feature flags позволяют постепенно внедрять новые функции приложения с уверенностью, проводить A/B тестирование новых интерфейсов, дополнять стратегию разработки на основе trunk и многое другое.
#Установка
Сначала установите 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 для сохранения значений feature flags.
Pennant поддерживает хранение разрешённых значений feature flags в массиве в памяти через драйвер array. Также Pennant может сохранять значения в реляционной базе данных через драйвер database, который используется по умолчанию.
#Определение функций
Для определения функции вы можете использовать метод 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),
};
}
}
Классы функций разрешаются через контейнер, поэтому вы можете внедрять зависимости в конструктор класса функции при необходимости.
#Настройка имени сохраняемой функции
По умолчанию Pennant сохраняет полное имя класса функции. Если вы хотите отделить имя функции от внутренней структуры приложения, можно указать свойство $name в классе функции. Значение этого свойства будет сохранено вместо имени класса:
<?php
namespace App\Features;
class NewApi
{
/**
* Сохраняемое имя функции.
*
* @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
{
/**
* Отобразить список ресурсов.
*/
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']);
// Проверить, активна ли хотя бы одна из указанных функций...
Feature::someAreActive(['new-api', 'site-redesign']);
// Проверить, неактивна ли функция...
Feature::inactive('new-api');
// Проверить, неактивны ли все указанные функции...
Feature::allAreInactive(['new-api', 'site-redesign']);
// Проверить, неактивна ли хотя бы одна из указанных функций...
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
{
/**
* Display a listing of the resource.
*/
public function index(Request $request): Response
{
return Feature::when(NewApi::class,
fn () => $this->resolveNewApiResponse($request),
fn () => $this->resolveLegacyApiResponse($request),
);
}
// ...
}
Метод unless является противоположностью when и выполняет первое замыкание, если функция неактивна:
return Feature::unless(NewApi::class,
fn () => $this->resolveLegacyApiResponse($request),
fn () => $this->resolveNewApiResponse($request),
);
#Трейт HasFeatures
Трейт HasFeatures из Pennant можно добавить в модель 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 предоставляет доступ ко многим другим удобным методам для работы с функциями:
// Значения...
$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, Pennant предлагает директиву @feature:
@feature('site-redesign')
<!-- 'site-redesign' активна -->
@else
<!-- 'site-redesign' неактивна -->
@endfeature
#Middleware
Pennant также включает middleware, который можно использовать, чтобы проверить, имеет ли текущий аутентифицированный пользователь доступ к функциональности ещё до вызова маршрута. Вы можете назначить этот middleware на маршрут и указать функциональности, необходимые для доступа к маршруту. Если какая-либо из указанных функциональностей неактивна для текущего аутентифицированного пользователя, маршрут вернёт 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'));
#Настройка ответа
Если вы хотите настроить ответ middleware при неактивности одной из указанных функций, используйте метод 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');
// Теперь эквивалентно...
Feature::for($user->team)->active('billing-v2');
#Область с возможностью null
Если область, передаваемая при проверке функции, равна null, и определение функции не поддерживает null через nullable-тип или объединение с null, Pennant автоматически вернёт false как результат функции.
Если scope, который вы передаёте фиче, может быть null, и вы хотите, чтобы функция разрешения значения фичи вызывалась, учтите это в её определении. null scope может возникнуть, если вы проверяете фичу внутри команды Artisan, задания в очереди или неаутентифицированного маршрута. Поскольку в этих контекстах обычно нет аутентифицированного пользователя, scope по умолчанию будет null.
Если вы не всегда явно задаёте scope для фичи, то убедитесь, что тип у scope — "nullable", и в логике определения фичи обрабатывайте значение null у scope:
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),
});
#Идентификация области
Встроенные драйверы хранения 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 morph map, можно настроить Pennant использовать morph map, чтобы отделить сохранённую функцию от структуры приложения.
Для этого после определения morph map в сервис-провайдере вызовите метод 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' активен -->
@elsefeature('purchase-button', 'seafoam-green')
<!-- 'seafoam-green' активен -->
@elsefeature('purchase-button', 'tart-orange')
<!-- 'tart-orange' активен -->
@endfeature
При использовании расширенных значений функция считается "активной", если её значение не равно false.
При вызове условного метода when расширенное значение функции передаётся в первое замыкание:
Feature::when('purchase-button',
fn ($color) => /* ... */,
fn () => /* ... */,
);
Аналогично, при вызове условного метода unless расширенное значение функции передаётся во второе замыкание:
Feature::unless('purchase-button',
fn () => /* ... */,
fn ($color) => /* ... */,
);
#Получение нескольких функций
Метод values позволяет получить несколько функций для заданной области:
Feature::values(['billing-v2', 'purchase-button']);
// [
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// ]
Или можно использовать метод all, чтобы получить значения всех определённых функций для области:
Feature::all();
// [
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// 'site-redesign' => true,
// ]
Однако классовые функции регистрируются динамически и не известны Pennant, пока явно не проверены. Это значит, что классовые функции вашего приложения могут не появиться в результатах метода all, если они ещё не были проверены в текущем запросе.
Чтобы гарантировать включение классовых функций при использовании метода all, можно воспользоваться возможностями обнаружения функций Pennant. Для начала вызовите метод discover в одном из сервис-провайдеров приложения:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* Загрузка сервисов приложения.
*/
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;
// Активировать функцию для области по умолчанию...
Feature::activate('new-api');
// Деактивировать функцию для заданной области...
Feature::for($user->team)->deactivate('billing-v2');
Также можно вручную установить расширенное значение функции, передав второй аргумент в метод activate:
Feature::activate('purchase-button', 'seafoam-green');
Чтобы заставить Pennant забыть сохранённое значение функции, используйте метод forget. При следующей проверке функция будет разрешена заново из определения:
Feature::forget('purchase-button');
#Массовые обновления
Для массового обновления сохранённых значений функций используйте методы activateForEveryone и deactivateForEveryone.
Например, если вы уверены в стабильности функции new-api и выбрали лучший цвет 'purchase-button' для оформления заказа, можно обновить сохранённое значение для всех пользователей:
use Laravel\Pennant\Feature;
Feature::activateForEveryone('new-api');
Feature::activateForEveryone('purchase-button', 'seafoam-green');
Или деактивировать функцию для всех пользователей:
Feature::deactivateForEveryone('new-api');
Это обновит только сохранённые значения, хранящиеся драйвером Pennant. Вам также нужно обновить определение функции в приложении.
#Очистка функций
Иногда полезно полностью очистить функцию из хранилища. Это обычно нужно, если функция удалена из приложения или вы изменили её определение и хотите распространить изменения на всех пользователей.
Для удаления всех сохранённых значений функции используйте метод purge:
// Очистка одной функции...
Feature::purge('new-api');
// Очистка нескольких функций...
Feature::purge(['new-api', 'purchase-button']);
Если нужно очистить все функции из хранилища, вызовите метод purge без аргументов:
Feature::purge();
Поскольку очистка функций может быть полезна в процессе деплоя приложения, Pennant включает Artisan-команду pennant:purge, которая очищает указанные функции из хранилища:
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
#Тестирование
При тестировании кода, работающего с feature flags, самый простой способ контролировать возвращаемое значение — переопределить функцию. Например, если в сервис-провайдере определена функция:
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
#Реализация драйвера
Если ни один из существующих драйверов хранения 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 генерирует различные события, которые могут быть полезны для отслеживания feature flags в вашем приложении.
#Laravel\Pennant\Events\RetrievingKnownFeature
Это событие вызывается при первом получении известной функции в запросе для конкретной области. Оно полезно для создания и отслеживания метрик по используемым feature flags.
#Laravel\Pennant\Events\RetrievingUnknownFeature
Это событие вызывается при первом получении неизвестной функции в запросе для конкретной области. Оно полезно, если вы планировали удалить feature flag, но случайно оставили ссылки на него в приложении.
Например, можно слушать это событие и 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
Это событие вызывается при первом динамическом вызове классовой функции в запросе.