Laravel Pennant
- Вступ
- Встановлення
- Конфігурація
- Визначення функцій
- Перевірка можливостей
- Область
- Багаті Значення Функцій
- Отримання декількох функцій
- Жадібне завантаження
- Оновлення значень
- Тестування
- Додавання користувацьких драйверів Pennant
- Події
Вступ
Laravel Pennant - це простий і легкий пакет для управління функціональними прапорами без зайвих елементів. Функціональні прапори дозволяють поступово впроваджувати нові функції застосунку з упевненістю, проводити A/B тестування нових дизайнів інтерфейсу, доповнювати стратегію розробки на основі trunk та багато іншого.
Встановлення
Спочатку встановіть Pennant у ваш проєкт, використовуючи менеджер пакетів компонувальник:
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 App\Models\User;
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),
};
}
}
Якщо ви хочете вручну вирішити екземпляр функції на основі класу, ви можете викликати метод instance на фасаді Feature:
use Illuminate\Support\Facades\Feature;
$instance = Feature::instance(NewApi::class);
Класи функцій вирішуються через контейнер, тому ви можете впроваджувати залежності в конструктор класу функцій, коли це необхідно.
Налаштування Імені Збереженої Функції
За замовчуванням, 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 { /** * Відобразити список даних ресурсу. */ 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
Pennant's HasFeatures трейт може бути доданий до моделі User вашого застосунку (або будь-якої іншої моделі, яка має функції), щоб забезпечити зручний спосіб перевірки функцій безпосередньо з моделі:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Pennant\Concerns\HasFeatures;
class User extends Authenticatable
{
use HasFeatures;
// ...
}
Після того як трейта було додано до вашої моделі, ви можете легко перевірити функції, викликавши метод features:
if ($user->features()->active('new-api')) {
// ...
}
Звичайно, метод features надає доступ до багатьох інших зручних методів для взаємодії з функціями:
// Значення... $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 та @featureany:
@feature('site-redesign') <!-- 'site-redesign' активна --> @else <!-- 'site-redesign' неактивна --> @endfeature @featureany(['site-redesign', 'beta']) <!-- 'site-redesign' або `beta` активна --> @endfeatureany
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, наданий middleware 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); } ); // ... }
Перехоплення Перевірок Функцій
Іноді може бути корисно виконати деякі перевірки в пам'яті перед отриманням збереженого значення певної функції. Уявіть, що ви розробляєте новий API за допомогою прапора функції і хочете мати можливість вимкнути новий API, не втрачаючи жодного з вирішених значень функції в сховищі. Якщо ви помітили помилку в новому API, ви могли б легко вимкнути його для всіх, крім внутрішніх членів команди, виправити помилку, а потім знову увімкнути новий API для користувачів, які раніше мали доступ до функції.
Ви можете досягти цього за допомогою функціоналу на основі класів методу before. Коли він присутній, метод before завжди виконується в пам'яті перед отриманням значення з сховища. Якщо з методу повертається значення, відмінне від null, воно буде використовуватися замість збереженого значення функціоналу протягом запиту:
<?php namespace App\Features; use App\Models\User; use Illuminate\Support\Facades\Config; use Illuminate\Support\Lottery; class NewApi { /** * Виконати перевірку, яка завжди зберігається в пам’яті, перед отриманням збереженого значення. */ public function before(User $user): mixed { if (Config::get('features.new-api.disabled')) { return $user->isInternalTeamMember(); } } /** * Отримати початкове значення функціональності. */ public function resolve(User $user): mixed { return match (true) { $user->isInternalTeamMember() => true, $user->isHighTrafficCustomer() => false, default => Lottery::odds(1 / 100), }; } }
Ви також можете використовувати цю функцію для планування глобального розгортання функції, яка раніше була за feature flag:
<?php namespace App\Features; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Config; class NewApi { /** * Виконати перевірку, яка завжди зберігається в пам’яті, перед отриманням збереженого значення. */ public function before(User $user): mixed { if (Config::get('features.new-api.disabled')) { return $user->isInternalTeamMember(); } if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) { return true; } } // ... }
Кеш у пам'яті
Коли перевіряється функція, Pennant створить кеш результату в пам'яті. Якщо ви використовуєте драйвер database, це означає, що повторна перевірка того ж прапора функції в межах одного запиту не викличе додаткових запитів до бази даних. Це також забезпечує, що функція має послідовний результат протягом усього запиту.
Якщо вам потрібно вручну очистити кеш у пам'яті, ви можете скористатися методом flushCache, який пропонує фасад Feature:
Feature::flushCache();
Область застосування
Вказання Області
Як обговорювалося, функції зазвичай перевіряються щодо поточного автентифікованого користувача. Однак це може не завжди відповідати вашим потребам. Тому можливо вказати область, щодо якої ви хочете перевірити дану функцію, за допомогою методу 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('/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');
Nullable Scope
Якщо область, яку ви надаєте при перевірці функції, є 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), }; } }
Серіалізація Scope
За замовчуванням Pennant використовуватиме повністю кваліфіковане ім'я класу при зберіганні функції, пов'язаної з моделлю Eloquent. Якщо ви вже використовуєте морф-карту Eloquent, ви можете вибрати, щоб Pennant також використовував морф-карту для відокремлення збереженої функції від структури вашого застосунку.
Щоб досягти цього, після визначення вашої Eloquent 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.
Коли викликається метод conditional 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);
}
}
Припускаючи, що ми використовуємо драйвер бази даних, цей код виконуватиме запит до бази даних для кожного користувача в циклі - виконуючи потенційно сотні запитів. Однак, використовуючи метод Pennant load, ми можемо усунути цю потенційну проблему продуктивності, завантажуючи значення функцій для колекції користувачів або областей заздалегідь:
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',
]);
Ви можете завантажити всі визначені функції, використовуючи метод loadAll:
Feature::for($users)->loadAll();
Оновлення значень
Коли значення функції вирішується вперше, базовий драйвер зберігатиме результат у сховищі. Це часто необхідно для забезпечення послідовного досвіду для ваших користувачів під час запитів. Однак іноді ви можете захотіти вручну оновити збережене значення функції.
Щоб досягти цього, ви можете використовувати методи activate та deactivate для перемикання функції "увімкнено" або "вимкнено":
use Laravel\Pennant\Feature;
// Активуйте функцію для області за замовчуванням...
Feature::activate('new-api');
// Деактивувати функцію для заданої області...
Feature::for($user->team)->deactivate('billing-v2');
Також можливо вручну встановити багате значення для функції, надаючи другий аргумент методу activate:
Feature::activate('purchase-button', 'seafoam-green');
Щоб вказати Pennant забути збережене значення для функції, ви можете використовувати метод forget. Коли функція буде перевірена знову, Pennant визначить значення функції з її визначення:
Feature::forget('purchase-button');
Масові Оновлення
Щоб оновити збережені значення функцій оптом, ви можете використовувати методи activateForEveryone та deactivateForEveryone.
Наприклад, уявіть, що ви тепер впевнені в стабільності функції new-api і визначили найкращий колір 'purchase-button' для вашого процесу оформлення замовлення - ви можете відповідно оновити збережене значення для всіх користувачів:
use Laravel\Pennant\Feature;
Feature::activateForEveryone('new-api');
Feature::activateForEveryone('purchase-button', 'seafoam-green');
Альтернативно, ви можете деактивувати цю функцію для всіх користувачів:
Feature::deactivateForEveryone('new-api');
Це оновить лише вирішені значення функцій, які були збережені драйвером зберігання Pennant. Вам також потрібно оновити визначення функції у вашому застосунку.
Очищення функцій
Іноді може бути корисно видалити всю функцію з сховища. Це зазвичай необхідно, якщо ви видалили функцію з вашого застосунку або внесли зміни до визначення функції, які ви хочете впровадити для всіх користувачів.
Ви можете видалити всі збережені значення для функції, використовуючи метод purge:
// Очищення однієї функції...
Feature::purge('new-api');
// Очищення декількох функцій...
Feature::purge(['new-api', 'purchase-button']);
Якщо ви хочете видалити всі функції з сховища, ви можете викликати метод purge без жодних аргументів:
Feature::purge();
Оскільки може бути корисним очищати функції в рамках конвеєра розгортання вашого застосунку, Pennant включає 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
Тестування
Коли тестуєте код, що взаємодіє з функціями, найпростіший спосіб контролювати значення, яке повертає функція у ваших тестах, це просто перевизначити функцію. Наприклад, уявіть, що у вас є наступна функція, визначена в одному з сервіс-провайдерів вашого застосунку:
use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;
Feature::define('purchase-button', fn () => Arr::random([
'blue-sapphire',
'seafoam-green',
'tart-orange',
]));
Щоб змінити значення, яке повертається функцією у ваших тестах, ви можете перевизначити функцію на початку тесту. Наступний тест завжди буде проходити, навіть якщо реалізація Arr::random() все ще присутня в сервіс-провайдері:
use Laravel\Pennant\Feature;
test('it can control feature values', function () {
Feature::define('purchase-button', 'seafoam-green');
expect(Feature::value('purchase-button'))->toBe('seafoam-green');
});
use Laravel\Pennant\Feature;
public function test_it_can_control_feature_values()
{
Feature::define('purchase-button', 'seafoam-green');
$this->assertSame('seafoam-green', Feature::value('purchase-button'));
}
Той самий підхід може бути використаний для функцій на основі класів:
use Laravel\Pennant\Feature;
test('it can control feature values', function () {
Feature::define(NewApi::class, true);
expect(Feature::value(NewApi::class))->toBeTrue();
});
use App\Features\NewApi;
use Laravel\Pennant\Feature;
public function test_it_can_control_feature_values()
{
Feature::define(NewApi::class, true);
$this->assertTrue(Feature::value(NewApi::class));
}
Якщо ваша функція повертає екземпляр Lottery, є кілька корисних допоміжних функцій для тестування.
Конфігурація зберігання
Ви можете налаштувати сховище, яке Pennant буде використовувати під час тестування, визначивши змінну середовища 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::define. Якщо це так, ваш користувацький драйвер також повинен реалізовувати інтерфейс Laravel\Pennant\Contracts\DefinesFeaturesExternally:
<?php
namespace App\Extensions;
use Laravel\Pennant\Contracts\Driver;
use Laravel\Pennant\Contracts\DefinesFeaturesExternally;
class FeatureFlagServiceDriver implements Driver, DefinesFeaturesExternally
{
/**
* Отримати функції, визначені для даної області.
*/
public function definedFeaturesForScope(mixed $scope): array {}
/* ... */
}
Метод definedFeaturesForScope повинен повертати список назв функцій, визначених для наданої області.
Події
Pennant відправляє різноманітні події, які можуть бути корисними при відстеженні прапорців функцій у вашому застосунку.
Laravel\Pennant\Events\FeatureRetrieved
Ця подія відправляється щоразу, коли перевіряється функція. Ця подія може бути корисною для створення та відстеження метрик використання прапора функцій у вашому застосунку.
Laravel\Pennant\Events\FeatureResolved
Ця подія відправляється вперше, коли значення функції вирішується для певної області.
Laravel\Pennant\Events\UnknownFeatureResolved
Ця подія відправляється вперше, коли невідома функція вирішується для певної області. Прослуховування цієї події може бути корисним, якщо ви мали намір видалити прапор функції, але випадково залишили розрізнені посилання на нього у вашому застосунку:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Laravel\Pennant\Events\UnknownFeatureResolved;
class AppServiceProvider extends ServiceProvider
{
/**
* Завантажте будь-які сервіси застосунку.
*/
public function boot(): void
{
Event::listen(function (UnknownFeatureResolved $event) {
Log::error("Resolving unknown feature [{$event->feature}].");
});
}
}
Laravel\Pennant\Events\DynamicallyRegisteringFeatureClass
Ця подія відправляється, коли функція на основі класу динамічно перевіряється вперше під час запиту.
Laravel\Pennant\Events\UnexpectedNullScopeEncountered
Ця подія відправляється, коли null область передається до визначення функції, яка не підтримує null.
Ця ситуація обробляється коректно, і функція поверне false. Однак, якщо ви хочете відмовитися від стандартної коректної поведінки цієї функції, ви можете зареєструвати слухача для цієї події в методі boot вашого застосунку AppServiceProvider:
use Illuminate\Support\Facades\Log;
use Laravel\Pennant\Events\UnexpectedNullScopeEncountered;
/**
* Запустіть будь-які сервіси застосунку.
*/
public function boot(): void
{
Event::listen(UnexpectedNullScopeEncountered::class, fn () => abort(500));
}
Laravel\Pennant\Events\FeatureUpdated
Ця подія відправляється під час оновлення функції для області, зазвичай шляхом виклику activate або deactivate.
Laravel\Pennant\Events\FeatureUpdatedForAllScopes
Ця подія відправляється при оновленні функції для всіх областей, зазвичай шляхом виклику activateForEveryone або deactivateForEveryone.
Laravel\Pennant\Events\FeatureDeleted
Ця подія відправляється при видаленні функції для області, зазвичай шляхом виклику forget.
Laravel\Pennant\Events\FeaturesPurged
Ця подія відправляється під час очищення певних функцій.
Laravel\Pennant\Events\AllFeaturesPurged
Ця подія відправляється при очищенні всіх функцій.
