1. Paquetes
  2. Laravel Pennant

Introducción

Laravel Pennant es un paquete de banderas de características simple y ligero, sin exceso. Las banderas de características te permiten implementar gradualmente nuevas funciones de la aplicación con confianza, realizar pruebas A/B de nuevos diseños de interfaz, complementar una estrategia de desarrollo basada en tronco y mucho más.

Instalación

En primer lugar, instala Pennant en tu proyecto usando el gestor de paquetes Composer:

composer require laravel/pennant

A continuación, debes publicar los archivos de configuración y migración de Pennant usando el comando Artisan vendor:publish:

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

Finalmente, debes ejecutar las migraciones de base de datos de tu aplicación. Esto creará una tabla features que Pennant utiliza para alimentar su controlador database:

php artisan migrate

Configuración

Después de publicar los activos de Pennant, su archivo de configuración estará ubicado en config/pennant.php. Este archivo de configuración te permite especificar el mecanismo de almacenamiento predeterminado que utilizará Pennant para almacenar los valores de las banderas de características resueltas.

Pennant incluye soporte para almacenar valores de banderas de características resueltas en un array en memoria a través del controlador array. Además, Pennant puede almacenar valores de banderas de características resueltas de manera persistente en una base de datos relacional a través del controlador database, que es el mecanismo de almacenamiento predeterminado utilizado por Pennant.

Definición de Características

Para definir una característica, puedes usar el método define ofrecido por la fachada Feature. Deberás proporcionar un nombre para la característica, así como un cierre que se invocará para resolver el valor inicial de la característica.

Normalmente, las características se definen en un proveedor de servicios utilizando la fachada Feature. El cierre recibirá el "ámbito" para la verificación de la característica. Más comúnmente, el ámbito es el usuario actualmente autenticado. En este ejemplo, definiremos una característica para implementar incrementalmente una nueva API para los usuarios de nuestra aplicación:

<?php
 
namespace App\Providers;
 
use App\Models\User;
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Inicializa cualquier servicio de la aplicación.
*/
public function boot(): void
{
Feature::define('new-api', fn (User $user) => match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
});
}
}

Como puedes ver, tenemos las siguientes reglas para nuestra característica:

  • Todos los miembros internos del equipo deben estar utilizando la nueva API.
  • Ningún cliente de alto tráfico debe estar utilizando la nueva API.
  • De lo contrario, la característica debería asignarse aleatoriamente a los usuarios con una probabilidad del 1 entre 100 de estar activa.

La primera vez que se verifica la característica new-api para un usuario dado, el resultado del cierre se almacenará en el controlador de almacenamiento. La próxima vez que se verifique la característica contra el mismo usuario, el valor se recuperará del almacenamiento y el cierre no se invocará.

Para mayor comodidad, si una definición de característica solo devuelve una lotería, puedes omitir completamente el cierre:

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

Características Basadas en Clases

Pennant también te permite definir características basadas en clases. A diferencia de las definiciones de características basadas en cierres, no es necesario registrar una característica basada en clases en un proveedor de servicios. Para crear una característica basada en clases, puedes invocar el comando Artisan pennant:feature. Por defecto, la clase de característica se colocará en el directorio app/Features de tu aplicación:

php artisan pennant:feature NewApi

Al escribir una clase de característica, solo necesitas definir un método resolve, que se invocará para resolver el valor inicial de la característica para un ámbito dado. Nuevamente, el ámbito será típicamente el usuario actualmente autenticado:

<?php
 
namespace App\Features;
 
use Illuminate\Support\Lottery;
 
class NewApi
{
/**
* Resuelve el valor inicial de la característica.
*/
public function resolve(User $user): mixed
{
return match (true) {
$user->isInternalTeamMember() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 100),
};
}
}

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

Comprobación de Características

Para determinar si una característica está activa, puedes utilizar el método active en la fachada Feature. Por defecto, las características se verifican contra el usuario actualmente autenticado:

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
 
class PodcastController
{
/**
* Muestra una lista de los recursos.
*/
public function index(Request $request): Response
{
return Feature::active('new-api')
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
}
 
// ...
}

Aunque por defecto las características se verifican contra el usuario actualmente autenticado, puedes verificar fácilmente la característica contra otro usuario o ámbito. Para lograr esto, utiliza el método for ofrecido por la fachada Feature:

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

Pennant también ofrece algunos métodos adicionales de conveniencia que pueden resultar útiles al determinar si una característica está activa o no:

// Determina si todas las características dadas están activas...
Feature::allAreActive(['new-api', 'site-redesign']);
 
// Determine if any of the given features are active...
Feature::someAreActive(['new-api', 'site-redesign']);
 
// Determine if a feature is inactive...
Feature::inactive('new-api');
 
// Determine if all of the given features are inactive...
Feature::allAreInactive(['new-api', 'site-redesign']);
 
// Determine if any of the given features are inactive...
Feature::someAreInactive(['new-api', 'site-redesign']);

Nota Cuando uses Pennant fuera de un contexto HTTP, como en un comando Artisan o un trabajo en cola, debes especificar explícitamente el ámbito de la característica. Alternativamente, puedes definir un ámbito predeterminado que tenga en cuenta tanto los contextos HTTP autenticados como los no autenticados.

Comprobación de Características Basadas en Clases

Para características basadas en clases, debes proporcionar el nombre de la clase al verificar la característica:

<?php
 
namespace App\Http\Controllers;
 
use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
 
class PodcastController
{
/**
* Muestra una lista de los recursos.
*/
public function index(Request $request): Response
{
return Feature::active(NewApi::class)
? $this->resolveNewApiResponse($request)
: $this->resolveLegacyApiResponse($request);
}
 
// ...
}

Ejecución Condicional

El método when se puede utilizar para ejecutar de manera fluida un cierre dado si una característica está activa. Además, se puede proporcionar un segundo cierre que se ejecutará si la característica está inactiva:

<?php
 
namespace App\Http\Controllers;
 
use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;
 
class PodcastController
{
/**
* Muestra una lista de los recursos.
*/
public function index(Request $request): Response
{
return Feature::when(NewApi::class,
fn () => $this->resolveNewApiResponse($request),
fn () => $this->resolveLegacyApiResponse($request),
);
}
 
// ...
}

El método unless sirve como el inverso del método when, ejecutando el primer cierre si la característica está inactiva:

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

El Rasgo HasFeatures

El rasgo HasFeatures de Pennant se puede agregar al modelo User de tu aplicación (o a cualquier otro modelo que tenga características) para proporcionar una forma fluida y conveniente de verificar características directamente desde el modelo:

<?php
 
namespace App\Models;
 
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Pennant\Concerns\HasFeatures;
 
class User extends Authenticatable
{
use HasFeatures;
 
// ...
}

Una vez que el rasgo se haya agregado a tu modelo, puedes verificar fácilmente las características invocando el método features:

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

Por supuesto, el método features proporciona acceso a muchos otros métodos convenientes para interactuar con características:

// Valores...
$value = $user->features()->value('purchase-button')
$values = $user->features()->values(['new-api', 'purchase-button']);
 
// State...
$user->features()->active('new-api');
$user->features()->allAreActive(['new-api', 'server-api']);
$user->features()->someAreActive(['new-api', 'server-api']);
 
$user->features()->inactive('new-api');
$user->features()->allAreInactive(['new-api', 'server-api']);
$user->features()->someAreInactive(['new-api', 'server-api']);
 
// Conditional execution...
$user->features()->when('new-api',
fn () => /* ... */,
fn () => /* ... */,
);
 
$user->features()->unless('new-api',
fn () => /* ... */,
fn () => /* ... */,
);

Directiva Blade

Para hacer que la verificación de características en Blade sea una experiencia fluida, Pennant ofrece una directiva @feature:

@feature('site-redesign')
<!-- 'site-redesign' is active -->
@else
<!-- 'site-redesign' is inactive -->
@endfeature

Middleware

Pennant también incluye un middleware que se puede utilizar para verificar que el usuario actualmente autenticado tenga acceso a una característica antes de que se invoque una ruta. Puedes asignar el middleware a una ruta y especificar las características que se requieren para acceder a la ruta. Si alguna de las características especificadas está inactiva para el usuario actualmente autenticado, la ruta devolverá una respuesta HTTP 400 Bad Request. Se pueden pasar varias características al método estático using.

use Illuminate\Support\Facades\Route;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
 
Route::get('/api/servers', function () {
// ...
})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api'));

Personalización de la Respuesta

Si deseas personalizar la respuesta que devuelve el middleware cuando una de las características enumeradas está inactiva, puedes utilizar el método whenInactive proporcionado por el middleware EnsureFeaturesAreActive. Normalmente, este método se debería invocar dentro del método boot de uno de los proveedores de servicios de tu aplicación:

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
 
/**
* Inicializa cualquier servicio de la aplicación.
*/
public function boot(): void
{
EnsureFeaturesAreActive::whenInactive(
function (Request $request, array $features) {
return new Response(status: 403);
}
);
 
// ...
}

Caché en Memoria

Al verificar una característica, Pennant creará una caché en memoria del resultado. Si estás utilizando el controlador database, esto significa que volver a verificar la misma bandera de características dentro de una sola solicitud no desencadenará consultas adicionales a la base de datos. Esto también garantiza que la característica tenga un resultado consistente durante la duración de la solicitud.

Si necesitas eliminar manualmente la caché en memoria, puedes utilizar el método flushCache ofrecido por la fachada Feature:

Feature::flushCache();

Ámbito

Especificación del Ámbito

Como se discutió, las características se verifican típicamente contra el usuario actualmente autenticado. Sin embargo, esto puede no adaptarse siempre a tus necesidades. Por lo tanto, es posible especificar el ámbito con el que deseas verificar una determinada característica a través del método for de la fachada Feature:

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

Por supuesto, los ámbitos de características no se limitan a "usuarios". Imagina que has construido una nueva experiencia de facturación que estás implementando para equipos enteros en lugar de usuarios individuales. Tal vez quieras que los equipos más antiguos tengan un despliegue más lento que los equipos más nuevos. Tu cierre de resolución de características podría parecerse a lo siguiente:

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

Notarás que el cierre que hemos definido no espera un User, sino que espera un modelo Team. Para determinar si esta característica está activa para el equipo de un usuario, debes pasar el equipo al método for ofrecido por la fachada Feature:

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

Ámbito Predeterminado

También es posible personalizar el ámbito predeterminado que Pennant utiliza para verificar características. Por ejemplo, tal vez todas tus características se verifican contra el equipo del usuario actualmente autenticado en lugar del usuario. En lugar de tener que llamar a Feature::for($user->team) cada vez que verificas una característica, puedes especificar el equipo como el ámbito predeterminado. Normalmente, esto se debe hacer en uno de los proveedores de servicios de tu aplicación:

<?php
 
namespace App\Providers;
 
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Inicializa cualquier servicio de la aplicación.
*/
public function boot(): void
{
Feature::resolveScopeUsing(fn ($driver) => Auth::user()?->team);
 
// ...
}
}

Si no se proporciona explícitamente un ámbito mediante el método for, la verificación de la característica ahora usará el equipo del usuario actualmente autenticado como el ámbito predeterminado:

Feature::active('billing-v2');
 
// Is now equivalent to...
 
Feature::for($user->team)->active('billing-v2');

Ámbito Anulable

Si el ámbito que proporcionas al verificar una característica es null y la definición de la característica no admite null mediante un tipo anulable o incluyendo null en un tipo de unión, Pennant devolverá automáticamente false como el valor de resultado de la característica.

Entonces, si el ámbito que estás pasando a una característica es potencialmente null y quieres que se invoque el resolvedor de valores de la característica, debes tenerlo en cuenta en la definición de tu característica. Un ámbito null puede ocurrir si verificas una característica dentro de un comando Artisan, un trabajo en cola o una ruta no autenticada. Dado que generalmente no hay un usuario autenticado en estos contextos, el ámbito predeterminado será null.

Si no siempre especificas explícitamente el ámbito de tu característica, debes asegurarte de que el tipo de ámbito sea "anulable" y manejar el valor de ámbito null dentro de la lógica de definición de tu característica:

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

Identificación del Ámbito

Los controladores de almacenamiento integrados de Pennant, como los de tipo array y database, saben cómo almacenar adecuadamente identificadores de ámbito para todos los tipos de datos de PHP, así como para los modelos Eloquent. Sin embargo, si tu aplicación utiliza un controlador de Pennant de terceros, es posible que ese controlador no sepa cómo almacenar adecuadamente un identificador para un modelo Eloquent u otros tipos personalizados en tu aplicación.

En este sentido, Pennant te permite formatear los valores de ámbito para el almacenamiento mediante la implementación del contrato FeatureScopeable en los objetos de tu aplicación que se utilizan como ámbitos de Pennant.

Por ejemplo, imagina que estás utilizando dos controladores de características diferentes en una sola aplicación: el controlador integrado database y un controlador de terceros llamado "Flag Rocket". El controlador "Flag Rocket" no sabe cómo almacenar adecuadamente un modelo Eloquent. En su lugar, requiere una instancia de FlagRocketUser. Al implementar el método toFeatureIdentifier definido por el contrato FeatureScopeable, podemos personalizar el valor de ámbito almacenable proporcionado a cada controlador utilizado por nuestra aplicación:

<?php
 
namespace App\Models;
 
use FlagRocket\FlagRocketUser;
use Illuminate\Database\Eloquent\Model;
use Laravel\Pennant\Contracts\FeatureScopeable;
 
class User extends Model implements FeatureScopeable
{
/**
* Convierte el objeto en un identificador de ámbito de característica para el controlador dado.
*/
public function toFeatureIdentifier(string $driver): mixed
{
return match($driver) {
'database' => $this,
'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id),
};
}
}

Serialización del Ámbito

De forma predeterminada, Pennant usará un nombre de clase completamente calificado al almacenar una característica asociada con un modelo Eloquent. Si ya estás utilizando un mapa morfológico Eloquent, puedes optar por hacer que Pennant también use el mapa morfológico para desvincular la característica almacenada de la estructura de tu aplicación.

Para lograr esto, después de definir tu mapa morfológico Eloquent en un proveedor de servicios, puedes invocar el método useMorphMap de la fachada Feature:

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

Valores de Características Enriquecidos

Hasta ahora, hemos mostrado principalmente las características como estando en un estado binario, lo que significa que están "activas" o "inactivas", pero Pennant también te permite almacenar valores ricos.

Por ejemplo, imagina que estás probando tres nuevos colores para el botón "Comprar ahora" de tu aplicación. En lugar de devolver true o false desde la definición de la característica, puedes devolver una cadena:

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

Puedes recuperar el valor de la característica purchase-button utilizando el método value:

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

La directiva Blade incluida en Pennant también facilita la renderización condicional de contenido basada en el valor actual de la característica:

@feature('purchase-button', 'blue-sapphire')
<!-- 'blue-sapphire' is active -->
@elsefeature('purchase-button', 'seafoam-green')
<!-- 'seafoam-green' is active -->
@elsefeature('purchase-button', 'tart-orange')
<!-- 'tart-orange' is active -->
@endfeature

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

Cuando llamas al método condicional when, el valor rico de la característica se proporcionará al primer cierre:

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

De manera similar, al llamar al método condicional unless, el valor rico de la característica se proporcionará al segundo cierre opcional:

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

Recuperación de Múltiples Características

El método values permite la recuperación de varias características para un ámbito dado:

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

O puedes usar el método all para recuperar los valores de todas las características definidas para un ámbito dado:

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

Sin embargo, las características basadas en clases se registran dinámicamente y Pennant no las conoce hasta que se verifican explícitamente. Esto significa que las características basadas en clases de tu aplicación pueden no aparecer en los resultados devueltos por el método all si aún no se han verificado durante la solicitud actual.

Si deseas asegurarte de que las clases de características siempre se incluyan al usar el método all, puedes aprovechar las capacidades de descubrimiento de características de Pennant. Para empezar, invoca el método discover en uno de los proveedores de servicios de tu aplicación:

<?php
 
namespace App\Providers;
 
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Inicializa cualquier servicio de la aplicación.
*/
public function boot(): void
{
Feature::discover();
 
// ...
}
}

El método discover registrará todas las clases de características en el directorio app/Features de tu aplicación. El método all incluirá ahora estas clases en sus resultados, independientemente de si se han verificado durante la solicitud actual:

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

Carga Ansiosa

Aunque Pennant mantiene una caché en memoria de todas las características resueltas para una sola solicitud, aún es posible encontrar problemas de rendimiento. Para aliviar esto, Pennant ofrece la capacidad de cargar de forma anticipada los valores de las características.

Para ilustrar esto, imagina que estás verificando si una característica está activa dentro de un bucle:

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

Suponiendo que estás utilizando el controlador de base de datos, este código ejecutará una consulta de base de datos por cada usuario en el bucle, ejecutando potencialmente cientos de consultas. Sin embargo, utilizando el método load de Pennant, podemos eliminar este posible cuello de botella de rendimiento al cargar de forma anticipada los valores de las características para una colección de usuarios o ámbitos:

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

Para cargar valores de características solo cuando aún no se hayan cargado, puedes usar el método loadMissing:

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

Actualización de Valores

Cuando el valor de una característica se resuelve por primera vez, el controlador subyacente almacenará el resultado en el almacenamiento. Esto a menudo es necesario para asegurar una experiencia consistente para tus usuarios entre las solicitudes. Sin embargo, en ocasiones, es posible que desees actualizar manualmente el valor almacenado de la característica.

Para lograr esto, puedes usar los métodos activate e deactivate para activar o desactivar una característica:

use Laravel\Pennant\Feature;
 
// Activate the feature for the default scope...
Feature::activate('new-api');
 
// Deactivate the feature for the given scope...
Feature::for($user->team)->deactivate('billing-v2');

También es posible establecer manualmente un valor rico para una característica proporcionando un segundo argumento al método activate:

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

Para indicarle a Pennant que olvide el valor almacenado para una característica, puedes usar el método forget. Cuando se verifica la característica nuevamente, Pennant resolverá el valor de la característica a partir de su definición de característica:

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

Actualizaciones Masivas

Para actualizar los valores almacenados de las características de forma masiva, puedes usar los métodos activateForEveryone y deactivateForEveryone.

Por ejemplo, imagina que ahora tienes confianza en la estabilidad de la característica new-api y has elegido el mejor color 'purchase-button' para tu flujo de pago; puedes actualizar el valor almacenado para todos los usuarios en consecuencia:

use Laravel\Pennant\Feature;
 
Feature::activateForEveryone('new-api');
 
Feature::activateForEveryone('purchase-button', 'seafoam-green');

Alternativamente, puedes desactivar la característica para todos los usuarios:

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

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

Eliminación de Características

A veces, puede ser útil purgar por completo una característica del almacenamiento. Esto es típicamente necesario si has eliminado la característica de tu aplicación o has realizado ajustes en la definición de la característica que te gustaría implementar para todos los usuarios.

Puedes eliminar todos los valores almacenados para una característica mediante el método purge:

// Eliminando una única característica...
Feature::purge('new-api');
 
// Purging multiple features...
Feature::purge(['new-api', 'purchase-button']);

Si deseas purgar todas las características del almacenamiento, puedes invocar el método purge sin argumentos:

Feature::purge();

Como puede ser útil purgar características como parte del flujo de implementación de tu aplicación, Pennant incluye un comando Artisan pennant:purge:

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

Pruebas

Al probar código que interactúa con banderas de características, la forma más fácil de controlar el valor devuelto de la bandera de características en tus pruebas es simplemente volver a definir la característica. Por ejemplo, imagina que tienes la siguiente característica definida en uno de los proveedores de servicios de tu aplicación:

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

Para modificar el valor devuelto de la característica en tus pruebas, puedes volver a definir la característica al principio de la prueba. La siguiente prueba siempre pasará, incluso si la implementación de Arr::random() aún está presente en el proveedor de servicios:

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

El mismo enfoque se puede utilizar para características basadas en clases:

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

Si tu característica devuelve una instancia de Lottery, hay algunos helpers de prueba útiles disponibles.

Configuración de Almacenamiento

Puedes configurar el almacén que Pennant utilizará durante las pruebas definiendo la variable de entorno PENNANT_STORE en el archivo phpunit.xml de tu aplicación:

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

Añadir Controladores de Características Personalizados

Implementación del Controlador

Si ninguno de los controladores de almacenamiento existentes de Pennant se adapta a las necesidades de tu aplicación, puedes escribir tu propio controlador de almacenamiento. Tu controlador personalizado debe implementar la interfaz 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 {}
}

Ahora, solo necesitamos implementar cada uno de estos métodos utilizando una conexión Redis. Para ver un ejemplo de cómo implementar cada uno de estos métodos, echa un vistazo al Laravel\Pennant\Drivers\DatabaseDriver en el código fuente de Pennant

Nota Laravel no incluye un directorio para contener tus extensiones. Eres libre de colocarlas donde desees. En este ejemplo, hemos creado un directorio Extensions para albergar el RedisFeatureDriver.

Registro del Controlador

Una vez que tu controlador haya sido implementado, estás listo para registrarlo con Laravel. Para agregar controladores adicionales a Pennant, puedes usar el método extend proporcionado por la fachada Feature. Debes llamar al método extend desde el método boot de uno de los proveedores de servicios de tu aplicación:

<?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
{
/**
* Registra cualquier servicio de la aplicación.
*/
public function register(): void
{
// ...
}
 
/**
* Inicializa cualquier servicio de la aplicación.
*/
public function boot(): void
{
Feature::extend('redis', function (Application $app) {
return new RedisFeatureDriver($app->make('redis'), $app->make('events'), []);
});
}
}

Una vez que el controlador ha sido registrado, puedes usar el controlador redis en el archivo de configuración config/pennant.php de tu aplicación:

'stores' => [
 
'redis' => [
'driver' => 'redis',
'connection' => null,
],
 
// ...
 
],

Eventos

Pennant despacha una variedad de eventos que pueden ser útiles para rastrear banderas de características en toda tu aplicación.

Laravel\Pennant\Events\RetrievingKnownFeature

Este evento se despacha la primera vez que se recupera una característica conocida durante una solicitud para un ámbito específico. Este evento puede ser útil para crear y realizar un seguimiento de métricas contra las banderas de características que se están utilizando en toda tu aplicación.

Laravel\Pennant\Events\RetrievingUnknownFeature

Este evento se despacha la primera vez que se recupera una característica desconocida durante una solicitud para un ámbito específico. Este evento puede ser útil si tienes la intención de eliminar una bandera de características, pero es posible que hayas dejado accidentalmente algunas referencias sueltas en toda tu aplicación.

Por ejemplo, puede resultar útil escuchar este evento y reportar o lanzar una excepción cuando ocurra:

<?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
{
/**
* Registra cualquier otro evento para tu aplicación.
*/
public function boot(): void
{
Event::listen(function (RetrievingUnknownFeature $event) {
report("Resolving unknown feature [{$event->feature}].");
});
}
}

Laravel\Pennant\Events\DynamicallyDefiningFeature

Este evento se despacha cuando se está verificando dinámicamente una característica basada en clases por primera vez durante una solicitud.