PHP Exception
Aprende las excepciones PHP: cómo lanzar y capturar errores con try/catch/finally, métodos de la clase Exception, multi-catch y clases de excepción personalizadas.
¿Qué es una Excepción?
Una excepción es un objeto que representa un error o una condición inesperada que interrumpe el flujo normal de un programa. En lugar de dejar que un problema rompa el script silenciosamente, lanzas una excepción en el punto donde algo sale mal y luego la capturas en algún lugar que sepa cómo recuperarse, registrarla o reportarla.
Esta página explica cómo lanzar y capturar excepciones con try/catch/finally, los métodos que te ofrece cada objeto de excepción, cómo capturar varios tipos de excepciones y cómo escribir tus propias clases de excepción.
Las excepciones son útiles cuando una función no puede continuar de manera significativa: entrada de usuario no válida, una conexión a la base de datos fallida, un archivo faltante o un valor fuera del rango permitido. Mantienen la lógica de manejo de errores separada de la lógica normal, para que el "camino feliz" sea más legible.
throw new Exception('Something went wrong');En PHP 7 y versiones posteriores, tanto Exception como Error implementan la interfaz Throwable, por lo que cualquier cosa que pueda lanzarse es un Throwable.
Lanzar y Capturar con try / catch / finally
Envuelves el código arriesgado en un bloque try. Si una sentencia dentro lanza una excepción, PHP deja de ejecutar el resto del bloque try y salta al primer catch coincidente. El bloque finally opcional siempre se ejecuta después, tanto si se lanzó una excepción como si no, lo que lo convierte en el lugar adecuado para liberar recursos (cerrar un archivo, un manejador de base de datos, un bloqueo).
<?php
function divide($a, $b) {
if ($b === 0) {
throw new InvalidArgumentException('Division by zero is not allowed.');
}
return $a / $b;
}
try {
echo divide(10, 2), "\n"; // 5
echo divide(10, 0), "\n"; // throws — the next line never runs
} catch (InvalidArgumentException $e) {
echo 'Caught: ' . $e->getMessage() . "\n";
} finally {
echo "Done.\n";
}Salida:
5
Caught: Division by zero is not allowed.
Done.Observa que el segundo echo divide(...) nunca se imprime, porque el lanzamiento aborta el bloque try de inmediato. El bloque finally sigue ejecutándose.
Leer Información de una Excepción
Cada objeto de excepción lleva detalles útiles. Los métodos más comunes, todos heredados de la clase base Exception, son:
| Método | Devuelve |
|---|---|
getMessage() | El mensaje de error legible por humanos |
getCode() | El código de error entero que pasaste al constructor |
getFile() | El archivo donde se creó la excepción |
getLine() | El número de línea donde fue creada |
getTraceAsString() | La pila de llamadas como string, útil para registro |
getPrevious() | La excepción anterior, cuando una envuelve a otra |
<?php
class InsufficientFundsException extends Exception {}
class Account {
private float $balance;
public function __construct(float $balance) { $this->balance = $balance; }
public function withdraw(float $amount): void {
if ($amount > $this->balance) {
throw new InsufficientFundsException(
"Cannot withdraw $amount; balance is {$this->balance}.",
100 // a custom error code
);
}
$this->balance -= $amount;
}
}
$account = new Account(50);
try {
$account->withdraw(80);
} catch (InsufficientFundsException $e) {
echo $e->getMessage() . "\n"; // Cannot withdraw 80; balance is 50.
echo 'Code: ' . $e->getCode() . "\n"; // Code: 100
}Salida:
Cannot withdraw 80; balance is 50.
Code: 100Capturar Múltiples Tipos de Excepciones
Un único try puede tener varios bloques catch, que se comprueban de arriba hacia abajo: el primero cuyo tipo coincide con la excepción lanzada gana. Si dos tipos de excepción no relacionados deben manejarse de la misma manera, combínalos en un bloque con el operador | (pipe) en lugar de duplicar código.
<?php
function parseAge(string $input): int {
if (!is_numeric($input)) {
throw new TypeError("'$input' is not a number.");
}
$age = (int) $input;
if ($age < 0) {
throw new RangeException("Age cannot be negative.");
}
return $age;
}
foreach (['42', 'abc', '-5'] as $value) {
try {
echo parseAge($value) . "\n";
} catch (TypeError | RangeException $e) {
echo get_class($e) . ': ' . $e->getMessage() . "\n";
}
}Salida:
42
TypeError: 'abc' is not a number.
RangeException: Age cannot be negative.El orden importa: dado que las excepciones PHP forman una jerarquía, coloca los tipos más específicos primero y los más amplios (como Exception o Throwable) al final; de lo contrario, el bloque amplio lo capturará todo antes de que el específico tenga oportunidad.
Crear Excepciones Personalizadas
Más allá de las clases integradas, puedes definir tus propios tipos de excepción extendiendo Exception (o una integrada más específica como RuntimeException). Una clase dedicada hace que tus bloques catch sean expresivos — puedes reaccionar a tu error específicamente — y te permite adjuntar datos adicionales.
En el ejemplo de Account anterior, InsufficientFundsException es una excepción personalizada. A menudo una subclase vacía es suficiente; añade métodos solo cuando necesites comportamiento adicional:
<?php
class ValidationException extends Exception {
private array $errors;
public function __construct(string $message, array $errors = []) {
parent::__construct($message);
$this->errors = $errors;
}
public function getErrors(): array {
return $this->errors;
}
}Si sobreescribes el constructor, llama siempre a parent::__construct() para que el mensaje, el código y la excepción anterior se configuren correctamente.
Capturar
Throwable. Para manejar tanto excepciones ordinarias como errores a nivel de motor (como unTypeErrorpor un tipo de argumento incorrecto), capturaThrowable. Es el "atrapa-todo" más seguro, pero úsalo como último recurso para no tragarte accidentalmente errores que deberías corregir:try { // risky code } catch (Throwable $e) { error_log($e->getMessage()); }
Buenas Prácticas
- Captura solo lo que puedas manejar. Deja que las excepciones de las que no puedas recuperarte se propaguen hasta un manejador central.
- Lanza pronto, captura tarde. Lanza en el punto exacto donde un valor se vuelve inválido; captura donde realmente puedas responder.
- Usa tipos específicos. Las subclases personalizadas o integradas superan a una
Exceptionbásica para el flujo de control. - Escribe mensajes informativos y usa
getCode()para la categorización legible por máquinas. - Registra, no silencies. Registra las excepciones con
error_log()en lugar de tragarlas. Para las excepciones no capturadas, registra un manejador global conset_exception_handler(). - No uses excepciones para el flujo normal. Resérvelas para condiciones genuinamente excepcionales.
Cómo Fluye el Control
El diagrama a continuación muestra el camino que sigue la ejecución a través de una estructura try/catch/finally:
graph TD
Try[Try Block] -->|No Exception| Finally[Finally Block]
Try -->|Exception Thrown| Catch[Catch Block]
Catch --> Finally