Kodokon kodokon.com

Excepciones: jerarquía, finally, manejador global

Construye una estrategia de errores completa: excepciones de negocio encadenadas, un finally controlado y manejadores globales como última línea de defensa.

9 min · 3 preguntas

Abrir esta lección en Kodokon

Desde PHP 7, todo lo que se lanza implementa Throwable, con dos ramas: Error (fallos del motor y de tipado, como TypeError) y Exception (fallos de la aplicación). Solo capturas Error en los límites del programa. Del lado de la aplicación, la SPL proporciona una jerarquía lista para usar: LogicException señala un error del desarrollador, que hay que corregir; RuntimeException, una contingencia en tiempo de ejecución, que hay que manejar. El reflejo profesional: deriva tus excepciones de negocio de estas bases, una por cada situación identificable.

PHP
<?php

declare(strict_types=1);

abstract class AppException extends RuntimeException
{
}

final class UserNotFound extends AppException
{
    public static function withEmail(
        string $email
    ): self {
        return new self("No user for $email");
    }
}

try {
    throw UserNotFound::withEmail('lea@example.com');
} catch (UserNotFound $e) {
    echo $e->getMessage();
}
Jerarquía de negocio: capturas UserNotFound, AppException o RuntimeException según el nivel.

try/catch solo merece la pena si sabes qué hacer con el error - de lo contrario, deja que suba. Tres herramientas refinan el control: el multi-catch catch (JsonException | ValueError $e) agrupa un manejo idéntico; el parámetro previous encadena la causa original cuando traduces una excepción técnica en una de negocio; finally se ejecuta en todos los casos - éxito, excepción, incluso un return anticipado - ideal para liberar un recurso.

PHP
<?php

declare(strict_types=1);

final class PayloadInvalid extends RuntimeException
{
}

function decode(string $json): mixed
{
    try {
        return json_decode(
            $json,
            true,
            flags: JSON_THROW_ON_ERROR,
        );
    } catch (JsonException $e) {
        throw new PayloadInvalid(
            'Invalid JSON payload',
            previous: $e,
        );
    } finally {
        echo "decode() finished\n";
    }
}

try {
    decode('{broken');
} catch (PayloadInvalid $e) {
    echo $e->getPrevious()?->getMessage();
}
Traducción de excepción: la causa técnica sigue accesible a través de getPrevious().

Al final de la cadena, dos manejadores globales cierran el montaje. set_exception_handler captura todo lo que no se manejó: registro estructurado, una respuesta 500 genérica - sin revelar nunca al usuario el mensaje ni la traza. set_error_handler convierte los errores heredados (warnings, notices) en ErrorException, unificando los dos mundos: todo se convierte en una excepción, todo sigue el mismo camino de manejo.

PHP
<?php

declare(strict_types=1);

set_error_handler(function (
    int $severity,
    string $message,
    string $file,
    int $line,
): bool {
    throw new ErrorException(
        $message,
        0,
        $severity,
        $file,
        $line,
    );
});

set_exception_handler(function (Throwable $e): void {
    error_log($e->getMessage());
    http_response_code(500);
    echo 'A technical error occurred.';
});

trigger_error('Legacy warning', E_USER_WARNING);
echo 'Never reached';
El warning se convierte en una excepción, no capturada: el manejador global responde, y luego el script se detiene.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué tienen en común Error y Exception en el PHP moderno?
    • Error hereda de Exception
    • Ambos implementan la interfaz Throwable
    • Ambos están reservados para el motor de PHP
  2. ¿Cuándo se ejecuta el bloque finally?
    • Solo si no se lanzó ninguna excepción
    • Solo después de un catch
    • En todos los casos: éxito, excepción capturada o no, return anticipado
  3. ¿Para qué sirve el parámetro previous de una excepción?
    • Preservar la excepción original cuando la traduces a una excepción de nivel superior
    • Impedir que la excepción suba por la pila de llamadas
    • Definir la excepción que se lanzará en el próximo fallo