Laravel y PHP
El paquete honk-me/honk-me funciona en cualquier app con PHP 8.3 y se integra a fondo con Laravel 13: una facade, un canal de notificaciones, informe de excepciones, hooks del programador de tareas y un fake para las pruebas.
- Paquete
-
honk-me/honk-mePublicado - Código fuente
- github.com/honk-me/honk-php · Licencia MIT
- Requisitos
- PHP 8.3 o posterior con ext-curl (o cualquier cliente PSR-18). La integración con Laravel es para Laravel 13.
Instalación
Añade el paquete, publica la configuración y pon en .env la URL del servidor y la clave de ingesta de un proyecto (nunca la subas al repositorio). honk:test envía un Bocinazo suave para comprobar la conexión.
composer require honk-me/honk-me
# publishes config/honk.php and adds HONK_URL and HONK_KEY to .env.example
php artisan honk:install HONK_URL=https://honk-me.app
HONK_KEY=honk_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx php artisan honk:test # sends a light honk and prints the message id
php artisan about --only=honk # URL, key prefix (never the secret), queue, Context metadata Quien tenga una clave de ingesta (honk_…) puede enviar mensajes a su proyecto. Guárdala en servidores, tareas y secretos de CI, nunca en un navegador ni en una app móvil o de escritorio.
Enviar un evento
La facade tiene un helper para cada nivel de la escala Honk. source toma por defecto tu APP_NAME, y environment, tu APP_ENV.
use HonkMe\Laravel\Facades\Honk;
Honk::beep('Backup finished', 'nightly pg_dump took 42 s');
Honk::loud('Disk 91%', '/var on app-01', ['groupKey' => 'disk/app-01/var']); El canal de notificaciones
Usa el canal honk como mail o database. Con notificaciones en cola, la respuesta al cliente no se retrasa, y la clave de idempotencia es el id de la notificación, así que los reintentos de la cola nunca avisan dos veces. Haz que toHonk() sea determinista: un reintento debe enviar el mismo contenido.
// app/Notifications/CustomerRequested.php
namespace App\Notifications;
use App\Models\CustomerRequest;
use HonkMe\Laravel\Notifications\HonkMessage;
use HonkMe\Laravel\Notifications\ToHonk;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\Attributes\Backoff;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Support\Str;
#[Tries(5)]
#[Backoff(10, 60, 300, 900)]
class CustomerRequested extends Notification implements ShouldQueue, ToHonk
{
use Queueable;
public function __construct(public CustomerRequest $request)
{
$this->afterCommit();
}
public function via(object $notifiable): array
{
return ['honk']; // add 'mail', 'database', … as you like
}
public function toHonk(object $notifiable): HonkMessage
{
$r = $this->request;
return HonkMessage::create()
->title(Str::limit("New request: {$r->subject}", 150))
->line("{$r->name} ({$r->company})")
->line(Str::limit($r->body, 2000)) // message: ≤ 8192 bytes
->light() // a light honk (info)
->category('customers')
->groupKey("requests/{$r->id}") // one group per request
->occurredAt($r->created_at)
->url(route('admin.requests.show', $r)) // https only, shown as "Open link"
// buttons, up to 3 (the first is the main one): https://, mailto:, tel: or sms:
->action('Reply by email', "mailto:{$r->email}")
->action('Call', "tel:{$r->phone}")
->meta('request_id', (string) $r->id);
}
} $request = CustomerRequest::create($validated);
$admin->notify(new CustomerRequested($request)); // a User with the Notifiable trait
Notification::route('honk', null)->notify(new CustomerRequested($request)); // or without a user Ahora, después de la respuesta o desde la cola
Honk::defer() envía después de la respuesta HTTP, en el mismo proceso. Honk::queue() envía desde un worker y sobrevive a los reinicios. Los helpers normales envían al momento, con reintentos incluidos. Tanto defer() como queue() validan el mensaje de inmediato, así que los errores aparecen en tu código.
use HonkMe\Laravel\Facades\Honk;
use HonkMe\Message;
Honk::defer()->beep('New order', "{$order->email} paid {$order->total} €"); // after the response
Honk::queue(Message::make('Imported 1 204 rows')->beep()->channel('imports')); // a queued job
Honk::loud('Disk 91%', '/var on app-01', ['groupKey' => 'disk/app-01/var']); // right now Avisos de excepciones
Una línea en bootstrap/app.php convierte en un Bocinazo largo cada excepción que reporta Laravel. Las repeticiones de un mismo fallo comparten grupo (exceptions/<Class>@<file>:<line>), y cada grupo avisa como mucho una vez cada 5 minutos. No hace nada mientras falten HONK_URL o HONK_KEY, y nunca lanza excepciones.
// bootstrap/app.php
use HonkMe\Laravel\Facades\Honk;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(Honk::reportable());
}) $exceptions->report(Honk::reportable(severity: 'blast', throttleSeconds: 60)); Tareas programadas
honkOnFailure() envía un problema con el código de salida y el final de la salida, y una recuperación la próxima vez que la tarea termina bien, pero solo después de un fallo, así que una tarea que funciona nunca te molesta.
// routes/console.php
Schedule::command('backup:run')->daily()->honkOnFailure(); // problem on failure, recovery once it works again
Schedule::command('reports:send')->hourly()->honkOnSuccess(); // a beep after every successful run Pruebas con Honk::fake()
El fake registra lo que se enviaría o se pondría en cola y nunca toca la red. Valida igual que el cliente real, así que un mensaje no válido sigue haciendo fallar tu prueba.
use HonkMe\Laravel\Facades\Honk;
use HonkMe\Message;
use HonkMe\Severity;
Honk::fake();
$this->post(route('quote.store'), $data);
Honk::assertSent(fn (Message $m) => $m->groupKey === 'requests/1' && $m->severity === Severity::Light);
Honk::assertSentTimes(1);
Honk::assertNotSent(fn (Message $m) => $m->severity === Severity::Blast);
Honk::assertQueued(fn (Message $m, string $idempotencyKey) => $m->channel === 'imports');
Honk::assertNothingSent(); // also assertNothingQueued(), assertNothingOutgoing() PHP sin Laravel
Sin Laravel, crea un Client y llama a los mismos helpers. problem y recovery abren y cierran un incidente para una clave de grupo.
composer require honk-me/honk-me use HonkMe\Client;
$honk = new Client(url: getenv('HONK_URL'), key: getenv('HONK_KEY'));
$honk->beep('Backup finished', 'nightly pg_dump took 42 s'); $client->problem('db/backup', 'Backup failed', 'pg_dump exited with 1'); // a long honk by default
$client->recovery('db/backup', 'Backup OK', 'pg_dump finished in 41 s'); // a beep by default Errores
Todas las excepciones extienden HonkException y te dicen si tiene sentido reintentar. Una ValidationException es un error que hay que corregir; una reintentable se puede volver a poner en cola con la misma clave de idempotencia.
use HonkMe\Exception\HonkException;
use HonkMe\Exception\ValidationException;
try {
$honk->send($message, "order-{$order->id}-failed");
} catch (ValidationException $e) {
report($e); // a bug: $e->fields says what to fix
} catch (HonkException $e) {
if (!$e->isRetryable()) {
throw $e;
}
// retry later with $e->idempotencyKey, after $e->retryAfter seconds if set
} Reintentos que nunca envían dos veces
- Cada envío lleva una
Idempotency-Key: la tuya o un UUIDv7 nuevo. La misma clave se reutiliza en cada reintento y, durante 24 horas, Honk responde a una repetición con el id original yduplicate: true, así que una respuesta perdida nunca crea un segundo mensaje. - Solo se reintentan los errores de red, los tiempos de espera agotados,
429y5xx, con backoff exponencial y jitter completo, y nunca antes delRetry-Afterdel servidor. Las demás respuestas4xxno se reintentan nunca: hay que corregir la solicitud. - Cada intento se corta a los 5 segundos y todo se detiene a los 30. Una espera que superaría ese plazo, como una cuota diaria que se reinicia a medianoche, falla al momento y te dice cuándo reintentar.
- Los campos se validan antes de enviar, y todos los errores de validación se devuelven juntos.