Ir al contenido principal
Laravel, shipping fast.

Antes de escribir el código que llama a Statamic, decide qué se le permite pedir al resto de la aplicación:

// app/Services/License/LicenseContract.php
interface LicenseContract
{
    /** @return Collection<int, License> */
    public function all(): Collection;

    public function create(
        string $name,
        string $domain,
    ): License;

    public function delete(string $key): void;
}

Tres operaciones, y todas hablan en los tipos de la aplicación. Nada en esta interfaz menciona HTTP, Statamic ni un array. Eso es lo que la convierte en una frontera: el controlador depende de ella, y ella no depende de nada.

Un cliente, configurado una vez

Cada llamada al proveedor necesita la misma URL base, las mismas cabeceras y los mismos límites de tiempo. El cliente HTTP de Laravel te deja ponerle nombre a esa configuración. (Documentación de Laravel: HTTP Client › Macros.)

// app/Providers/AppServiceProvider.php
public function boot(): void
{
    Http::macro('statamic', function (string $token) {
        return Http::baseUrl(config('services.statamic.url'))
            ->withToken($token)
            ->acceptJson()
            ->connectTimeout(3)
            ->timeout(10);
    });
}

A partir de ahora Http::statamic($token) es un cliente que ya sabe dónde está Statamic y cuánto esperarlo. El token es un parámetro y no se lee de la configuración dentro de la macro, por una razón que cobra importancia en el Capítulo 4: quienes llaman usarán tokens distintos.

El driver

Un driver es una implementación del contrato para un proveedor. Es la única clase del proyecto que conoce las URL de Statamic:

// app/Services/License/StatamicDriver.php
class StatamicDriver implements LicenseContract
{
    public function __construct(private string $token) {}

    public function all(): Collection
    {
        $sites = $this->http()->get('/sites')
            ->throw()
            ->json('data');

        if (! is_array($sites)) {
            throw new MalformedProviderResponse(['data']);
        }

        return collect($sites)->map(
            fn ($row) => License::fromProvider((array) $row),
        );
    }
}

throw() convierte una respuesta fallida en una excepción. Un driver que devuelve una colección vacía cuando el proveedor está caído le está mintiendo a quien lo llama.

La comprobación is_array cierra una versión menos obvia del mismo error. Un proveedor en un mal día a veces responde 200 con una página de error en HTML. Sin la comprobación, esa página no tiene clave data, la lista vuelve vacía, y un consumidor concluye que no tiene licencias. El cast (array) es el mismo cuidado un nivel más abajo: un elemento que no es un array se convierte en uno que no pasa la validación, de modo que una lista mal formada termina en la excepción propia de la aplicación y nunca en un error de tipos.

Crear una licencia sigue la misma forma:

// app/Services/License/StatamicDriver.php
public function create(
    string $name,
    string $domain,
): License {
    $data = $this->http()->post('/sites', [
        'name' => $name,
        'domain' => $domain,
    ])->throw()->json('data');

    return License::fromProvider((array) $data);
}

delete encierra una decisión más:

// app/Services/License/StatamicDriver.php
public function delete(string $key): void
{
    $response = $this->http()
        ->withUrlParameters(['key' => $key])
        ->delete('/sites/{key}');

    if ($response->notFound()) {
        return;
    }

    $response->throw();
}

Un 404 al borrar se trata como un éxito. Quien llamó quería que la licencia desapareciera, y ha desaparecido. Esto importa en cuanto un borrado puede reintentarse, cosa que el Capítulo 10 hace posible: el segundo intento no debe fallar porque el primero funcionó.

withUrlParameters() coloca la clave en la ruta de forma segura. La clave llega de una URL que tecleó un consumidor, y un valor como abc?force=1 o ../admin concatenado en una ruta enviaría una petición que nunca quisiste enviar. La plantilla de URL la codifica.

No puede hacer nada con una clave que sea solamente .., que es un segmento de ruta válido y sube un nivel. Eso se cierra en la ruta. Route::pattern('license', '[A-Za-z0-9_-]+') en AppServiceProvider::boot() hace que una petición así ni siquiera coincida. (Documentación de Laravel: HTTP Client › URI Templates.)

No se pudo cargar el audio. Inténtalo de nuevo en un momento.