Ir al contenido principal
Laravel, shipping fast.

Este es el fallo con el que se abrió el capítulo. Una petición que falla produce un 500 y una excepción. Un job que falla aterriza en failed_jobs. Una tarea programada que nunca arranca no produce nada. Ni excepción, ni línea de log, ni fila. La entrada de cron se perdió en una migración de servidor, o la tarea lleva una semana sin ejecutarse porque un bloqueo nunca se liberó, y la primera señal es una tabla que lleva un mes creciendo.

No puedes alertar sobre un error que no ocurre. Alertas sobre la ausencia de un éxito. La línea de purga una vez más, con los dos tipos de alarma:

// routes/console.php
Schedule::command('model:prune')
    ->daily()
    ->onFailure(function (): void {
        report(new ScheduledTaskFailed('model:prune'));
    })
    ->pingOnSuccess(config('services.heartbeat.prune'));

onFailure() cubre la tarea que se ejecutó y devolvió un código de salida distinto de cero, y por eso importan los códigos de salida.

pingOnSuccess() cubre todo lo demás. Después de cada ejecución correcta llama a una URL de un servicio de monitorización, y ese servicio da la alarma cuando la llamada no llega a tiempo. Esto es un latido (heartbeat), a veces llamado interruptor de hombre muerto, y es el único tipo de monitor que atrapa una tarea que nunca se ejecutó. Usa pingOnSuccess() y no thenPing(), que llama a la URL también tras una ejecución fallida y así informa de un latido de una tarea que se está muriendo. La mayoría de los servicios de disponibilidad ofrecen latidos, y es una línea por tarea, más una URL por tarea en config/services.php. (Documentación de Laravel: Task Scheduling › Pinging URLs.)

Pon un latido en cada tarea programada cuya ausencia acabaría doliendo. Para esta API eso es la purga. En el servicio que aprovisiona sitios, es también el barrido de trabajo atascado del Capítulo 10.

Comandos que cambian datos

El Capítulo 17 construirá un comando que reescribe filas, un backfill. Tres hábitos se le aplican a él y a cualquier comando que cambie datos en producción.

Un ensayo en seco. --dry-run imprime lo que cambiaría y no cambia nada. Ejecútalo primero, lee la salida y después ejecútalo de verdad.

Seguro de ejecutar otra vez. Un comando interrumpido por un despliegue o una conexión perdida se ejecutará una segunda vez. Tiene que continuar, no empezar de nuevo ni hacer el trabajo dos veces.

Confirmación donde es destructivo. Un comando que borra debería preguntar antes de hacerlo, y negarse a ejecutarse desatendido en producción salvo que se le diga:

if (! $this->confirmToProceed()) {
    return self::FAILURE;
}

Añade el ConfirmableTrait al comando y esa línea pregunta en producción y pasa sin preguntar en los demás entornos. Declara {--force} en la firma y un script que va en serio puede saltarse la pregunta. Es como se comporta el propio migrate.

Probar comandos y programaciones

Un comando se prueba como un endpoint: lo llamas, y después compruebas la salida, el código de salida y lo que ahora es verdad.

// tests/Feature/IssueConsumerTokenTest.php
it('issues a token from the console', function () {
    $consumer = Consumer::factory()->create();

    $this->artisan('consumers:token', [
        'consumer' => $consumer->id,
        '--ability' => ['licenses:read'],
    ])
        ->expectsOutputToContain('Shown once')
        ->assertSuccessful();

    expect($consumer->tokens()->count())->toBe(1);
});

it('fails for a consumer that does not exist', function () {
    $this->artisan('consumers:token', ['consumer' => 999])
        ->assertFailed();
});

Para la programación, hay un comando que ejecuta una tarea ahora, sin esperar a su hora:

php artisan schedule:test

Lista las tareas programadas y ejecuta la que elijas. Úsalo después de cambiar una programación, antes de confiar en que el reloj lo haga por ti.

Y para cualquier cosa que dependa del paso del tiempo, Laravel puede mover el reloj:

it('prunes requests past the retention window', function () {
    ApiRequest::factory()->create();

    $this->travel(91)->days();

    $this->artisan('model:prune')->assertSuccessful();

    expect(ApiRequest::count())->toBe(0);
});

Ese test demuestra la política de retención que el Capítulo 17 pone en el modelo, que conserva una petición durante noventa días. ApiRequest recibe su factory de la manera habitual, con make:model -f. Falla el día que alguien cambie la ventana sin querer.

Resumen del capítulo 11

  • Un comando es un controlador para la terminal: una firma declarada, dependencias inyectadas, casi ninguna lógica.
  • Cuando una operación tiene dos entradas, pon sus reglas en un método y llámalo desde las dos.
  • Devuelve un código de salida real. Es la única parte de la salida que leen las máquinas.
  • Una sola entrada de cron ejecuta el planificador. La programación es código en routes/console.php, y schedule:list muestra lo que es en realidad.
  • Usa withoutOverlapping() y onOneServer(), con una caché compartida entre servidores.
  • El planificador despacha. El trabajo pesado va a la cola.
  • Una tarea que nunca se ejecuta no produce ningún error. Pon un latido en cada tarea cuya ausencia dolería.
  • Los comandos que cambian datos tienen un ensayo en seco, son seguros de ejecutar otra vez y confirman antes de destruir.

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