- Wprowadzenie
- Instalacja
- Konfiguracja
- Definiowanie Funkcji
- Sprawdzanie Funkcji
- Zakres
- Bogate Wartości Funkcji
- Pobieranie Wielu Funkcji
- Eager Loading
- Aktualizacja Wartości
- Testowanie
- Dodawanie Niestandardowych Sterowników Pennant
- Zdarzenia
Laravel Pennant to prosty i lekki pakiet flag funkcji - bez zbędnych dodatków. Flagi funkcji umożliwiają stopniowe wdrażanie nowych funkcji aplikacji z pewnością, testowanie A/B nowych projektów interfejsu, uzupełnienie strategii rozwoju opartej na trunk-based development i wiele więcej.
Najpierw zainstaluj Pennant w swoim projekcie za pomocą menedżera pakietów Composer:
composer require laravel/pennantNastępnie opublikuj pliki konfiguracji i migracji Pennant używając polecenia Artisan vendor:publish:
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"Na koniec uruchom migracje bazy danych aplikacji. To utworzy tabelę features, której Pennant używa do zasilania swojego sterownika database:
php artisan migratePo opublikowaniu zasobów Pennant, jego plik konfiguracyjny będzie znajdował się w config/pennant.php. Ten plik konfiguracyjny pozwala określić domyślny mechanizm przechowywania, który będzie używany przez Pennant do przechowywania rozwiązanych wartości flag funkcji.
Pennant zawiera wsparcie dla przechowywania rozwiązanych wartości flag funkcji w tablicy w pamięci za pomocą sterownika array. Lub Pennant może przechowywać rozwiązane wartości flag funkcji trwale w relacyjnej bazie danych za pomocą sterownika database, który jest domyślnym mechanizmem przechowywania używanym przez Pennant.
Aby zdefiniować funkcję, możesz użyć metody define oferowanej przez fasadę Feature. Musisz podać nazwę funkcji oraz domknięcie, które zostanie wywołane w celu rozwiązania początkowej wartości funkcji.
Typowo funkcje są definiowane w dostawcy usług za pomocą fasady Feature. Domknięcie otrzyma "zakres" dla sprawdzenia funkcji. Najczęściej zakresem jest aktualnie uwierzytelniony użytkownik. W tym przykładzie zdefiniujemy funkcję do stopniowego wdrażania nowego API dla użytkowników naszej aplikacji:
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrapuj wszelkie usługi aplikacji.
*/
public function boot(): void
{
Feature::define('new-api', fn (User $user) => match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
});
}
}Jak widać, mamy następujące reguły dla naszej funkcji:
- Wszyscy członkowie wewnętrznego zespołu powinni używać nowego API.
- Żaden klient o wysokim ruchu nie powinien używać nowego API.
- W przeciwnym razie funkcja powinna być losowo przypisana użytkownikom z szansą 1 na 100 bycia aktywną.
Pierwszym razem, gdy funkcja new-api jest sprawdzana dla danego użytkownika, wynik domknięcia zostanie zapisany przez sterownik przechowywania. Następnym razem, gdy funkcja jest sprawdzana dla tego samego użytkownika, wartość zostanie pobrana z przechowywania i domknięcie nie zostanie wywołane.
Dla wygody, jeśli definicja funkcji zwraca tylko loterię, możesz całkowicie pominąć domknięcie:
Feature::define('site-redesign', Lottery::odds(1, 1000));
Pennant pozwala również definiować funkcje oparte na klasach. W przeciwieństwie do definicji funkcji opartych na domknięciach, nie ma potrzeby rejestrowania funkcji opartej na klasie w dostawcy usług. Aby utworzyć funkcję opartą na klasie, możesz wywołać polecenie Artisan pennant:feature. Domyślnie klasa funkcji zostanie umieszczona w katalogu app/Features Twojej aplikacji:
php artisan pennant:feature NewApiPisząc klasę funkcji, musisz tylko zdefiniować metodę resolve, która zostanie wywołana w celu rozwiązania początkowej wartości funkcji dla danego zakresu. Ponownie, zakresem będzie zazwyczaj aktualnie uwierzytelniony użytkownik:
<?php
namespace App\Features;
use App\Models\User;
use Illuminate\Support\Lottery;
class NewApi
{
/**
* Rozwiąż początkową wartość funkcji.
*/
public function resolve(User $user): mixed
{
return match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
};
}
}Jeśli chcesz ręcznie rozwiązać instancję funkcji opartej na klasie, możesz wywołać metodę instance na fasadzie Feature:
use Illuminate\Support\Facades\Feature;
$instance = Feature::instance(NewApi::class);Note
Klasy funkcji są rozwiązywane przez kontener, więc możesz wstrzykiwać zależności do konstruktora klasy funkcji gdy potrzeba.
Domyślnie Pennant będzie przechowywał w pełni kwalifikowaną nazwę klasy funkcji. Jeśli chcesz oddzielić przechowywaną nazwę funkcji od wewnętrznej struktury aplikacji, możesz określić właściwość $name w klasie funkcji. Wartość tej właściwości zostanie zapisana zamiast nazwy klasy:
<?php
namespace App\Features;
class NewApi
{
/**
* Przechowywana nazwa funkcji.
*
* @var string
*/
public $name = 'new-api';
// ...
}Aby określić, czy funkcja jest aktywna, możesz użyć metody active na fasadzie Feature. Domyślnie funkcje są sprawdzane względem aktualnie uwierzytelnionego użytkownika:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
class PodcastController
{
/**
* Wyświetl listę zasobów.
*/
public function index(Request $request): Response
{
return Feature::active('new-api')
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
}
// ...
}Choć funkcje są domyślnie sprawdzane względem aktualnie uwierzytelnionego użytkownika, możesz łatwo sprawdzić funkcję względem innego użytkownika lub zakresu. Aby to osiągnąć, użyj metody for oferowanej przez fasadę Feature:
return Feature::for($user)->active('new-api')
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);Pennant oferuje również kilka dodatkowych wygodnych metod, które mogą okazać się przydatne przy określaniu, czy funkcja jest aktywna czy nie:
// Określ, czy wszystkie podane funkcje są aktywne...
Feature::allAreActive(['new-api', 'site-redesign']);
// Określ, czy którakolwiek z podanych funkcji jest aktywna...
Feature::someAreActive(['new-api', 'site-redesign']);
// Określ, czy funkcja jest nieaktywna...
Feature::inactive('new-api');
// Określ, czy wszystkie podane funkcje są nieaktywne...
Feature::allAreInactive(['new-api', 'site-redesign']);
// Określ, czy którakolwiek z podanych funkcji jest nieaktywna...
Feature::someAreInactive(['new-api', 'site-redesign']);Note
Używając Pennant poza kontekstem HTTP, takim jak polecenie Artisan lub zadanie w kolejce, powinieneś zazwyczaj jawnie określić zakres funkcji. Alternatywnie możesz zdefiniować domyślny zakres, który uwzględnia zarówno uwierzytelnione konteksty HTTP, jak i nieuwierzytelnione konteksty.
Dla funkcji opartych na klasach powinieneś podać nazwę klasy podczas sprawdzania funkcji:
<?php
namespace App\Http\Controllers;
use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
class PodcastController
{
/**
* Wyświetl listę zasobów.
*/
public function index(Request $request): Response
{
return Feature::active(NewApi::class)
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
}
// ...
}Metoda when może być użyta do płynnego wykonania danego domknięcia, jeśli funkcja jest aktywna. Dodatkowo może być podane drugie domknięcie, które zostanie wykonane, jeśli funkcja jest nieaktywna:
<?php
namespace App\Http\Controllers;
use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
class PodcastController
{
/**
* Wyświetl listę zasobów.
*/
public function index(Request $request): Response
{
return Feature::when(NewApi::class,
fn () => $this->resolveNewApiResponse($request),
fn () => $this->resolveLegacyApiResponse($request),
);
}
// ...
}Metoda unless służy jako odwrotność metody when, wykonując pierwsze domknięcie, jeśli funkcja jest nieaktywna:
return Feature::unless(NewApi::class,
fn () => $this->resolveLegacyApiResponse($request),
fn () => $this->resolveNewApiResponse($request),
);Trait HasFeatures Pennanta może być dodany do modelu User Twojej aplikacji (lub dowolnego innego modelu, który ma funkcje), aby zapewnić płynny i wygodny sposób sprawdzania funkcji bezpośrednio z modelu:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Pennant\Concerns\HasFeatures;
class User extends Authenticatable
{
use HasFeatures;
// ...
}Po dodaniu traita do modelu możesz łatwo sprawdzać funkcje wywołując metodę features:
if ($user->features()->active('new-api')) {
// ...
}Oczywiście metoda features zapewnia dostęp do wielu innych wygodnych metod do interakcji z funkcjami:
// Wartości...
$value = $user->features()->value('purchase-button')
$values = $user->features()->values(['new-api', 'purchase-button']);
// Stan...
$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']);
// Warunkowe wykonywanie...
$user->features()->when('new-api',
fn () => /* ... */,
fn () => /* ... */,
);
$user->features()->unless('new-api',
fn () => /* ... */,
fn () => /* ... */,
);Aby sprawdzanie funkcji w Blade było bezproblemowe, Pennant oferuje dyrektywy @feature i @featureany:
@feature('site-redesign')
<!-- 'site-redesign' jest aktywny -->
@else
<!-- 'site-redesign' jest nieaktywny -->
@endfeature
@featureany(['site-redesign', 'beta'])
<!-- 'site-redesign' lub `beta` jest aktywny -->
@endfeatureanyPennant zawiera również middleware, które może być użyte do weryfikacji, czy aktualnie uwierzytelniony użytkownik ma dostęp do funkcji, zanim trasa zostanie wywołana. Możesz przypisać middleware do trasy i określić funkcje, które są wymagane do dostępu do trasy. Jeśli którakolwiek z określonych funkcji jest nieaktywna dla aktualnie uwierzytelnionego użytkownika, trasa zwróci odpowiedź HTTP 400 Bad Request. Wiele funkcji może być przekazanych do statycznej metody using.
use Illuminate\Support\Facades\Route;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
Route::get('/api/servers', function () {
// ...
})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api'));Jeśli chcesz dostosować odpowiedź zwracaną przez middleware, gdy jedna z wymienionych funkcji jest nieaktywna, możesz użyć metody whenInactive udostępnianej przez middleware EnsureFeaturesAreActive. Zazwyczaj ta metoda powinna być wywołana w metodzie boot jednego z dostawców usług Twojej aplikacji:
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
/**
* Bootstrapuj wszelkie usługi aplikacji.
*/
public function boot(): void
{
EnsureFeaturesAreActive::whenInactive(
function (Request $request, array $features) {
return new Response(status: 403);
}
);
// ...
}Czasami może być przydatne wykonanie niektórych sprawdzeń w pamięci przed pobraniem przechowywanej wartości danej funkcji. Wyobraź sobie, że rozwijasz nowe API za flagą funkcji i chcesz mieć możliwość wyłączenia nowego API bez utraty żadnych rozwiązanych wartości funkcji w przechowywaniu. Jeśli zauważysz błąd w nowym API, możesz łatwo wyłączyć je dla wszystkich z wyjątkiem członków wewnętrznego zespołu, naprawić błąd, a następnie ponownie włączyć nowe API dla użytkowników, którzy wcześniej mieli dostęp do funkcji.
Możesz to osiągnąć za pomocą metody before funkcji opartej na klasie. Gdy jest obecna, metoda before jest zawsze uruchamiana w pamięci przed pobraniem wartości z przechowywania. Jeśli z metody zostanie zwrócona wartość inna niż null, zostanie ona użyta zamiast przechowywanej wartości funkcji na czas trwania żądania:
<?php
namespace App\Features;
use App\Models\User;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Lottery;
class NewApi
{
/**
* Uruchom sprawdzenie zawsze w pamięci przed pobraniem przechowywanej wartości.
*/
public function before(User $user): mixed
{
if (Config::get('features.new-api.disabled')) {
return $user->isInternalTeamMember();
}
}
/**
* Rozwiąż początkową wartość funkcji.
*/
public function resolve(User $user): mixed
{
return match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
};
}
}Możesz również użyć tej funkcji do zaplanowania globalnego wdrożenia funkcji, która wcześniej była za flagą funkcji:
<?php
namespace App\Features;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
class NewApi
{
/**
* Uruchom sprawdzenie zawsze w pamięci przed pobraniem przechowywanej wartości.
*/
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;
}
}
// ...
}Podczas sprawdzania funkcji Pennant utworzy cache w pamięci z wynikiem. Jeśli używasz sterownika database, oznacza to, że ponowne sprawdzenie tej samej flagi funkcji w ramach pojedynczego żądania nie spowoduje dodatkowych zapytań do bazy danych. Zapewnia to również, że funkcja ma spójny wynik przez czas trwania żądania.
Jeśli musisz ręcznie wyczyścić cache w pamięci, możesz użyć metody flushCache oferowanej przez fasadę Feature:
Feature::flushCache();Jak omówiono, funkcje są zazwyczaj sprawdzane względem aktualnie uwierzytelnionego użytkownika. Jednak może to nie zawsze odpowiadać Twoim potrzebom. Dlatego możliwe jest określenie zakresu, względem którego chcesz sprawdzić daną funkcję, za pomocą metody for fasady Feature:
return Feature::for($user)->active('new-api')
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);Oczywiście zakresy funkcji nie są ograniczone do "użytkowników". Wyobraź sobie, że zbudowałeś nowe doświadczenie rozliczeniowe, które wdrażasz dla całych zespołów, a nie indywidualnych użytkowników. Być może chcesz, aby starsze zespoły miały wolniejsze wdrożenie niż nowsze zespoły. Twoje domknięcie rozwiązywania funkcji może wyglądać mniej więcej tak:
use App\Models\Team;
use Illuminate\Support\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);
});Zauważysz, że zdefiniowane przez nas domknięcie nie oczekuje User, ale zamiast tego oczekuje modelu Team. Aby określić, czy ta funkcja jest aktywna dla zespołu użytkownika, powinieneś przekazać zespół do metody for oferowanej przez fasadę Feature:
if (Feature::for($user->team)->active('billing-v2')) {
return redirect('/billing/v2');
}
// ...Możliwe jest również dostosowanie domyślnego zakresu, którego Pennant używa do sprawdzania funkcji. Na przykład, może wszystkie Twoje funkcje są sprawdzane względem zespołu aktualnie uwierzytelnionego użytkownika zamiast użytkownika. Zamiast wywoływać Feature::for($user->team) za każdym razem, gdy sprawdzasz funkcję, możesz zamiast tego określić zespół jako domyślny zakres. Zazwyczaj powinno to być wykonane w jednym z dostawców usług Twojej aplikacji:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrapuj wszelkie usługi aplikacji.
*/
public function boot(): void
{
Feature::resolveScopeUsing(fn ($driver) => Auth::user()?->team);
// ...
}
}Jeśli żaden zakres nie jest jawnie podany przez metodę for, sprawdzenie funkcji będzie teraz używać zespołu aktualnie uwierzytelnionego użytkownika jako domyślnego zakresu:
Feature::active('billing-v2');
// Jest teraz równoważne...
Feature::for($user->team)->active('billing-v2');Jeśli zakres, który podajesz podczas sprawdzania funkcji, jest null i definicja funkcji nie obsługuje null przez typ nullable lub przez włączenie null w typ union, Pennant automatycznie zwróci false jako wartość wyniku funkcji.
Więc jeśli zakres, który przekazujesz do funkcji, jest potencjalnie null i chcesz, aby resolver wartości funkcji został wywołany, powinieneś to uwzględnić w definicji funkcji. Zakres null może wystąpić, jeśli sprawdzasz funkcję w poleceniu Artisan, zadaniu w kolejce lub nieuwierzytelnionej trasie. Ponieważ zazwyczaj nie ma uwierzytelnionego użytkownika w tych kontekstach, domyślny zakres będzie null.
Jeśli nie zawsze jawnie określasz zakres funkcji, powinieneś upewnić się, że typ zakresu jest "nullable" i obsłużyć wartość zakresu null w logice definicji funkcji:
use App\Models\User;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;
Feature::define('new-api', fn (User $user) => match (true) {// [tl! remove]
Feature::define('new-api', fn (User|null $user) => match (true) {// [tl! add]
$user === null => true,// [tl! add]
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
});Wbudowane sterowniki array i database Pennanta wiedzą, jak prawidłowo przechowywać identyfikatory zakresu dla wszystkich typów danych PHP, a także modeli Eloquent. Jednak jeśli Twoja aplikacja wykorzystuje sterownik Pennant innej firmy, ten sterownik może nie wiedzieć, jak prawidłowo przechowywać identyfikator dla modelu Eloquent lub innych niestandardowych typów w Twojej aplikacji.
W świetle tego Pennant pozwala formatować wartości zakresu do przechowywania przez implementację kontraktu FeatureScopeable na obiektach w Twojej aplikacji, które są używane jako zakresy Pennant.
Na przykład wyobraź sobie, że używasz dwóch różnych sterowników funkcji w jednej aplikacji: wbudowanego sterownika database i sterownika "Flag Rocket" innej firmy. Sterownik "Flag Rocket" nie wie, jak prawidłowo przechowywać model Eloquent. Zamiast tego wymaga instancji FlagRocketUser. Implementując toFeatureIdentifier zdefiniowany przez kontrakt FeatureScopeable, możemy dostosować wartość zakresu do przechowywania, dostarczaną każdemu sterownikowi używanemu przez naszą aplikację:
<?php
namespace App\Models;
use FlagRocket\FlagRocketUser;
use Illuminate\Database\Eloquent\Model;
use Laravel\Pennant\Contracts\FeatureScopeable;
class User extends Model implements FeatureScopeable
{
/**
* Rzutuj obiekt na identyfikator zakresu funkcji dla danego sterownika.
*/
public function toFeatureIdentifier(string $driver): mixed
{
return match($driver) {
'database' => $this,
'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id),
};
}
}Domyślnie Pennant będzie używać w pełni kwalifikowanej nazwy klasy podczas przechowywania funkcji powiązanej z modelem Eloquent. Jeśli już używasz mapy morph Eloquent, możesz wybrać, aby Pennant również używał mapy morph, aby oddzielić przechowywaną funkcję od struktury Twojej aplikacji.
Aby to osiągnąć, po zdefiniowaniu mapy morph Eloquent w dostawcy usług, możesz wywołać metodę useMorphMap fasady Feature:
use Illuminate\Database\Eloquent\Relations\Relation;
use Laravel\Pennant\Feature;
Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);
Feature::useMorphMap();Do tej pory pokazywaliśmy głównie funkcje jako będące w stanie binarnym, co oznacza, że są "aktywne" lub "nieaktywne", ale Pennant pozwala również przechowywać bogate wartości.
Na przykład wyobraź sobie, że testujesz trzy nowe kolory dla przycisku "Kup teraz" Twojej aplikacji. Zamiast zwracać true lub false z definicji funkcji, możesz zamiast tego zwrócić ciąg znaków:
use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;
Feature::define('purchase-button', fn (User $user) => Arr::random([
'blue-sapphire',
'seafoam-green',
'tart-orange',
]));Możesz pobrać wartość funkcji purchase-button używając metody value:
$color = Feature::value('purchase-button');Dołączona dyrektywa Blade Pennanta również ułatwia warunkowe renderowanie treści na podstawie aktualnej wartości funkcji:
@feature('purchase-button', 'blue-sapphire')
<!-- 'blue-sapphire' jest aktywny -->
@elsefeature('purchase-button', 'seafoam-green')
<!-- 'seafoam-green' jest aktywny -->
@elsefeature('purchase-button', 'tart-orange')
<!-- 'tart-orange' jest aktywny -->
@endfeatureNote
Używając bogatych wartości, ważne jest, aby wiedzieć, że funkcja jest uważana za "aktywną", gdy ma jakąkolwiek wartość inną niż false.
Podczas wywoływania warunkowej metody when, bogata wartość funkcji zostanie przekazana do pierwszego domknięcia:
Feature::when('purchase-button',
fn ($color) => /* ... */,
fn () => /* ... */,
);Podobnie, podczas wywoływania warunkowej metody unless, bogata wartość funkcji zostanie przekazana do opcjonalnego drugiego domknięcia:
Feature::unless('purchase-button',
fn () => /* ... */,
fn ($color) => /* ... */,
);Metoda values pozwala na pobieranie wielu funkcji dla danego zakresu:
Feature::values(['billing-v2', 'purchase-button']);
// [
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// ]Lub możesz użyć metody all, aby pobrać wartości wszystkich zdefiniowanych funkcji dla danego zakresu:
Feature::all();
// [
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// 'site-redesign' => true,
// ]Jednak funkcje oparte na klasach są dynamicznie rejestrowane i nie są znane Pennantowi, dopóki nie zostaną jawnie sprawdzone. Oznacza to, że funkcje oparte na klasach Twojej aplikacji mogą nie pojawić się w wynikach zwróconych przez metodę all, jeśli nie zostały już sprawdzone podczas bieżącego żądania.
Jeśli chcesz zapewnić, że klasy funkcji są zawsze uwzględniane podczas używania metody all, możesz użyć możliwości odkrywania funkcji Pennanta. Aby rozpocząć, wywołaj metodę discover w jednym z dostawców usług Twojej aplikacji:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrapuj wszelkie usługi aplikacji.
*/
public function boot(): void
{
Feature::discover();
// ...
}
}Metoda discover zarejestruje wszystkie klasy funkcji w katalogu app/Features Twojej aplikacji. Metoda all będzie teraz uwzględniać te klasy w swoich wynikach, niezależnie od tego, czy zostały sprawdzone podczas bieżącego żądania:
Feature::all();
// [
// 'App\Features\NewApi' => true,
// 'billing-v2' => false,
// 'purchase-button' => 'blue-sapphire',
// 'site-redesign' => true,
// ]Chociaż Pennant utrzymuje cache w pamięci wszystkich rozwiązanych funkcji dla pojedynczego żądania, nadal możliwe jest napotkanie problemów z wydajnością. Aby temu zaradzić, Pennant oferuje możliwość eager loadingu wartości funkcji.
Aby to zilustrować, wyobraź sobie, że sprawdzamy, czy funkcja jest aktywna w pętli:
use Laravel\Pennant\Feature;
foreach ($users as $user) {
if (Feature::for($user)->active('notifications-beta')) {
$user->notify(new RegistrationSuccess);
}
}Zakładając, że używamy sterownika bazy danych, ten kod wykona zapytanie do bazy danych dla każdego użytkownika w pętli - potencjalnie wykonując setki zapytań. Jednak używając metody load Pennanta, możemy usunąć to potencjalne wąskie gardło wydajności przez eager loading wartości funkcji dla kolekcji użytkowników lub zakresów:
Feature::for($users)->load(['notifications-beta']);
foreach ($users as $user) {
if (Feature::for($user)->active('notifications-beta')) {
$user->notify(new RegistrationSuccess);
}
}Aby załadować wartości funkcji tylko wtedy, gdy nie zostały jeszcze załadowane, możesz użyć metody loadMissing:
Feature::for($users)->loadMissing([
'new-api',
'purchase-button',
'notifications-beta',
]);Możesz załadować wszystkie zdefiniowane funkcje używając metody loadAll:
Feature::for($users)->loadAll();Gdy wartość funkcji jest rozwiązywana po raz pierwszy, podstawowy sterownik zapisze wynik w przechowywaniu. Jest to często konieczne, aby zapewnić spójne doświadczenie dla użytkowników w różnych żądaniach. Jednak czasami możesz chcieć ręcznie zaktualizować przechowywaną wartość funkcji.
Aby to osiągnąć, możesz użyć metod activate i deactivate, aby przełączać funkcję "włącz" lub "wyłącz":
use Laravel\Pennant\Feature;
// Aktywuj funkcję dla domyślnego zakresu...
Feature::activate('new-api');
// Dezaktywuj funkcję dla danego zakresu...
Feature::for($user->team)->deactivate('billing-v2');Możliwe jest również ręczne ustawienie bogatej wartości dla funkcji przez podanie drugiego argumentu do metody activate:
Feature::activate('purchase-button', 'seafoam-green');Aby poinstruować Pennant, aby zapomniał przechowywaną wartość funkcji, możesz użyć metody forget. Gdy funkcja zostanie ponownie sprawdzona, Pennant rozwiąże wartość funkcji z jej definicji funkcji:
Feature::forget('purchase-button');Aby zaktualizować przechowywane wartości funkcji zbiorczo, możesz użyć metod activateForEveryone i deactivateForEveryone.
Na przykład wyobraź sobie, że jesteś teraz pewien stabilności funkcji new-api i ustaliłeś najlepszy kolor 'purchase-button' dla przepływu kasy - możesz odpowiednio zaktualizować przechowywaną wartość dla wszystkich użytkowników:
use Laravel\Pennant\Feature;
Feature::activateForEveryone('new-api');
Feature::activateForEveryone('purchase-button', 'seafoam-green');Alternatywnie możesz dezaktywować funkcję dla wszystkich użytkowników:
Feature::deactivateForEveryone('new-api');Note
To zaktualizuje tylko rozwiązane wartości funkcji, które zostały zapisane przez sterownik przechowywania Pennanta. Będziesz także musiał zaktualizować definicję funkcji w swojej aplikacji.
Czasami może być przydatne wyczyszczenie całej funkcji z przechowywania. Jest to zazwyczaj konieczne, jeśli usunąłeś funkcję ze swojej aplikacji lub dokonałeś poprawek w definicji funkcji, które chcesz wdrożyć dla wszystkich użytkowników.
Możesz usunąć wszystkie przechowywane wartości funkcji używając metody purge:
// Czyszczenie pojedynczej funkcji...
Feature::purge('new-api');
// Czyszczenie wielu funkcji...
Feature::purge(['new-api', 'purchase-button']);Jeśli chcesz wyczyścić wszystkie funkcje z przechowywania, możesz wywołać metodę purge bez żadnych argumentów:
Feature::purge();Ponieważ może być przydatne czyszczenie funkcji w ramach pipeline'u wdrożenia aplikacji, Pennant zawiera polecenie Artisan pennant:purge, które wyczyści podane funkcje z przechowywania:
php artisan pennant:purge new-api
php artisan pennant:purge new-api purchase-buttonMożliwe jest również wyczyszczenie wszystkich funkcji z wyjątkiem tych na danej liście funkcji. Na przykład wyobraź sobie, że chcesz wyczyścić wszystkie funkcje, ale zachować wartości dla funkcji "new-api" i "purchase-button" w przechowywaniu. Aby to osiągnąć, możesz przekazać te nazwy funkcji do opcji --except:
php artisan pennant:purge --except=new-api --except=purchase-buttonDla wygody polecenie pennant:purge obsługuje również flagę --except-registered. Ta flaga wskazuje, że wszystkie funkcje z wyjątkiem tych jawnie zarejestrowanych w dostawcy usług powinny zostać wyczyszczone:
php artisan pennant:purge --except-registeredPodczas testowania kodu, który współdziała z flagami funkcji, najłatwiejszym sposobem kontrolowania zwracanej wartości flagi funkcji w testach jest po prostu ponowne zdefiniowanie funkcji. Na przykład wyobraź sobie, że masz następującą funkcję zdefiniowaną w jednym z dostawców usług Twojej aplikacji:
use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;
Feature::define('purchase-button', fn () => Arr::random([
'blue-sapphire',
'seafoam-green',
'tart-orange',
]));Aby zmodyfikować zwracaną wartość funkcji w testach, możesz ponownie zdefiniować funkcję na początku testu. Następujący test zawsze przejdzie, nawet jeśli implementacja Arr::random() nadal jest obecna w dostawcy usług:
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'));
}To samo podejście może być użyte dla funkcji opartych na klasach:
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));
}Jeśli Twoja funkcja zwraca instancję Lottery, dostępnych jest kilka przydatnych helperów testowych.
Możesz skonfigurować store, który Pennant będzie używać podczas testowania, definiując zmienną środowiskową PENNANT_STORE w pliku phpunit.xml Twojej aplikacji:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
<!-- ... -->
<php>
<env name="PENNANT_STORE" value="array"/>
<!-- ... -->
</php>
</phpunit>Jeśli żaden z istniejących sterowników przechowywania Pennanta nie pasuje do potrzeb Twojej aplikacji, możesz napisać własny sterownik przechowywania. Twój niestandardowy sterownik powinien implementować interfejs 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 {}
}Teraz musimy tylko zaimplementować każdą z tych metod używając połączenia Redis. Aby zobaczyć przykład, jak zaimplementować każdą z tych metod, spójrz na Laravel\Pennant\Drivers\DatabaseDriver w kodzie źródłowym Pennant
Note
Laravel nie jest dostarczany z katalogiem do zawierania Twoich rozszerzeń. Możesz umieścić je gdziekolwiek chcesz. W tym przykładzie utworzyliśmy katalog Extensions, aby pomieścić RedisFeatureDriver.
Po zaimplementowaniu sterownika jesteś gotowy, aby zarejestrować go w Laravel. Aby dodać dodatkowe sterowniki do Pennant, możesz użyć metody extend dostarczonej przez fasadę Feature. Powinieneś wywołać metodę extend z metody boot jednego z dostawców usług Twojej aplikacji:
<?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
{
/**
* Zarejestruj wszelkie usługi aplikacji.
*/
public function register(): void
{
// ...
}
/**
* Bootstrapuj wszelkie usługi aplikacji.
*/
public function boot(): void
{
Feature::extend('redis', function (Application $app) {
return new RedisFeatureDriver($app->make('redis'), $app->make('events'), []);
});
}
}Po zarejestrowaniu sterownika możesz użyć sterownika redis w pliku konfiguracyjnym config/pennant.php Twojej aplikacji:
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => null,
],
// ...
],Jeśli Twój sterownik jest wrapperem wokół platformy flag funkcji innej firmy, prawdopodobnie zdefiniujesz funkcje na platformie, a nie używając metody Feature::define Pennanta. Jeśli tak jest, Twój niestandardowy sterownik powinien również implementować interfejs Laravel\Pennant\Contracts\DefinesFeaturesExternally:
<?php
namespace App\Extensions;
use Laravel\Pennant\Contracts\Driver;
use Laravel\Pennant\Contracts\DefinesFeaturesExternally;
class FeatureFlagServiceDriver implements Driver, DefinesFeaturesExternally
{
/**
* Pobierz funkcje zdefiniowane dla danego zakresu.
*/
public function definedFeaturesForScope(mixed $scope): array {}
/* ... */
}Metoda definedFeaturesForScope powinna zwracać listę nazw funkcji zdefiniowanych dla podanego zakresu.
Pennant wysyła różnorodne zdarzenia, które mogą być przydatne podczas śledzenia flag funkcji w całej aplikacji.
To zdarzenie jest wysyłane za każdym razem, gdy funkcja jest sprawdzana. To zdarzenie może być przydatne do tworzenia i śledzenia metryk względem użycia flagi funkcji w całej aplikacji.
To zdarzenie jest wysyłane za pierwszym razem, gdy wartość funkcji jest rozwiązywana dla określonego zakresu.
To zdarzenie jest wysyłane za pierwszym razem, gdy nieznana funkcja jest rozwiązywana dla określonego zakresu. Słuchanie tego zdarzenia może być przydatne, jeśli zamierzałeś usunąć flagę funkcji, ale przypadkowo zostawiłeś przypadkowe odniesienia do niej w całej aplikacji:
<?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
{
/**
* Bootstrapuj wszelkie usługi aplikacji.
*/
public function boot(): void
{
Event::listen(function (UnknownFeatureResolved $event) {
Log::error("Resolving unknown feature [{$event->feature}].");
});
}
}To zdarzenie jest wysyłane, gdy funkcja oparta na klasie jest dynamicznie sprawdzana po raz pierwszy podczas żądania.
To zdarzenie jest wysyłane, gdy zakres null jest przekazywany do definicji funkcji, która nie obsługuje null.
Ta sytuacja jest obsługiwana gracefully i funkcja zwróci false. Jednak jeśli chcesz zrezygnować z tego domyślnego zachowania graceful funkcji, możesz zarejestrować listener dla tego zdarzenia w metodzie boot AppServiceProvider Twojej aplikacji:
use Illuminate\Support\Facades\Log;
use Laravel\Pennant\Events\UnexpectedNullScopeEncountered;
/**
* Bootstrapuj wszelkie usługi aplikacji.
*/
public function boot(): void
{
Event::listen(UnexpectedNullScopeEncountered::class, fn () => abort(500));
}To zdarzenie jest wysyłane podczas aktualizacji funkcji dla zakresu, zazwyczaj przez wywołanie activate lub deactivate.
To zdarzenie jest wysyłane podczas aktualizacji funkcji dla wszystkich zakresów, zazwyczaj przez wywołanie activateForEveryone lub deactivateForEveryone.
To zdarzenie jest wysyłane podczas usuwania funkcji dla zakresu, zazwyczaj przez wywołanie forget.
To zdarzenie jest wysyłane podczas czyszczenia określonych funkcji.
To zdarzenie jest wysyłane podczas czyszczenia wszystkich funkcji.