En PHP pones el código que puede fallar dentro de try { ... } y manejas el fallo en catch (Exception $e) { ... }. Si algo del bloque try lanza una excepción, PHP salta directamente al catch correspondiente, y $e->getMessage() te dice qué falló. Si no se lanza nada, el bloque catch se salta.
Cambia divide(10, 0) por divide(10, 5) y vuelve a ejecutarlo: no hay excepción, así que se imprime not printed y el bloque catch se salta. Sin try, la excepción terminaría el script con un error fatal "Uncaught". Cómo lanzar tus propias excepciones y escribir clases de excepción se explica en la página de excepciones.
La sintaxis de try catch
La forma completa tiene tres partes. Solo try es obligatorio, más al menos un catch o un finally:
try {
// code that may throw
} catch (SomeException $e) {
// runs if SomeException (or a subclass) was thrown
} catch (OtherException | ThirdException $e) {
// runs for either of these types
} finally {
// always runs, thrown or not
}
El tipo del catch decide qué captura: la propia clase y todas las clases que la extienden. Desde PHP 8.0 la variable es opcional cuando no la necesitas: catch (JsonException) { ... }.
Capturar Exception vs Error vs Throwable
Desde PHP 7, los fallos que detecta el propio PHP (llamar a un método que no existe, un tipo de argumento incorrecto, una división por cero) lanzan un Error, no una Exception, y PHP 8 convirtió muchos warnings antiguos en TypeError y ValueError. Los dos implementan la interfaz Throwable, pero ninguno extiende al otro, así que catch (Exception $e) no captura un Error:
Throwable
├── Exception (problems a program expects and handles)
│ ├── InvalidArgumentException, RuntimeException, JsonException, ...
└── Error (engine errors, usually a bug in the code)
├── TypeError
├── ValueError
├── ArithmeticError
│ └── DivisionByZeroError
└── UnhandledMatchError, ...
Ejecuta esto para ver qué catch maneja cada problema:
Entonces, ¿cuál deberías capturar?
catch (SomeSpecificException $e)cuando sabes qué puede fallar y qué hacer al respecto. Es la mayoría del código.catch (Exception $e)para "cualquier cosa que una librería o mi código lanzó a propósito".catch (Throwable $e)en el nivel más externo (un manejador de peticiones, un ejecutor de tareas) para registrar todos los fallos, incluidos los errores del propio PHP, y mostrar un mensaje amable.
Capturar varios tipos de excepción
Escribe varios bloques catch para manejar tipos distintos de formas distintas. PHP los prueba de arriba abajo y usa la primera coincidencia, así que un tipo específico debe ir antes que su padre, o el padre lo capturará todo primero. Cuando varios tipos reciben el mismo tratamiento, combínalos en un catch con |:
Las tres clases extienden Exception, así que un solo catch (Exception $e) también las capturaría, pero entonces el código ya no podría responder 404 a una y 400 a las demás.
finally: código que siempre se ejecuta
Un bloque finally se ejecuta después de try y catch sin importar cómo terminaron: con normalidad, con una excepción capturada, con una excepción que nadie capturó o con un return. Es el lugar para liberar lo que el bloque try haya obtenido: cerrar un archivo, liberar un bloqueo, parar un temporizador.
Con good.txt el archivo se cierra antes de que se imprima saved, porque finally se ejecuta antes de que la función devuelva realmente su valor. Con bad.txt no hay catch dentro de process(): finally cierra el archivo y luego la excepción sigue subiendo hasta el catch de quien llama.
Evita return dentro de finally: sustituye el valor que devolvió el bloque try y descarta en silencio una excepción que iba saliendo:
function f(): string
{
try {
return 'from try';
} finally {
return 'from finally';
}
}
function g(): string
{
try {
throw new RuntimeException('lost');
} finally {
return 'no exception reaches the caller';
}
}
echo f(); // from finally
echo g(); // no exception reaches the caller
Relanzar o envolver una excepción
A veces un bloque catch solo puede manejar en parte un problema: registrarlo, limpiar y dejar que continúe. throw $e; vuelve a lanzar la misma excepción. Más a menudo quieres añadir contexto conservando la causa original, y para eso está el tercer argumento del constructor, $previous:
Quien llama maneja una sola ConfigException y no necesita saber que la configuración es JSON, mientras que getPrevious() conserva el detalle de bajo nivel para el log.
Excepciones no capturadas y un manejador global
Una excepción que ningún catch maneja termina el script con un error fatal que incluye la clase, el mensaje, el archivo y la línea, y una traza de la pila:
<?php
// No try/catch anywhere in this file
function charge(int $cents): void
{
if ($cents <= 0) {
throw new DomainException("Amount must be positive, got $cents");
}
}
charge(500);
echo "first charge ok\n";
charge(-1);
echo "never printed\n";
PHP imprime first charge ok y luego:
PHP Fatal error: Uncaught DomainException: Amount must be positive, got -1 in /home/index.php:6
Stack trace:
#0 /home/index.php(12): charge()
#1 {main}
thrown in /home/index.php on line 6
En un sitio en producción ese mensaje nunca debería llegar a los visitantes. set_exception_handler() registra una función que recibe cualquier excepción que nadie capturó, un único lugar para registrarla e imprimir una página amable:
El manejador se ejecuta y luego el script se detiene; no hay forma de continuar después del throw. Que el propio texto de error de PHP se muestre o no en la página lo controla display_errors, explicado en reporte de errores.
Los warnings no son excepciones
try/catch solo ve lo que se lanza. Muchas funciones antiguas de PHP no lanzan nada: devuelven false o null y, como mucho, imprimen un warning. json_decode() es un caso típico, y envolverla en try no cambia nada hasta que le pides que lance excepciones:
Con funciones que no tienen esa opción, comprueba el valor de retorno (if ($handle === false)) o comprueba antes la condición previa (file_exists(), isset()). Un set_error_handler() que lanza ErrorException convierte cada warning en una excepción real si quieres una sola regla para todo.
Receta: validar la entrada de un formulario con excepciones
Un patrón práctico: las funciones de validación lanzan excepciones y la página las captura y muestra el mensaje junto al formulario. Ejecútalo, escribe una edad en el formulario y pulsa Send: el mismo script se ejecuta otra vez con $_POST lleno.
Prueba con un campo vacío, twelve, 9 y 30. Cada regla que falla lanza su propio mensaje, y un solo catch los convierte todos en la misma línea roja. La página de formularios amplía esto con varios campos y errores junto a cada uno.
Preguntas frecuentes
¿Cómo funciona try catch en PHP?
PHP ejecuta el código dentro de try { }. Si algo lanza una excepción, PHP se salta el resto del bloque try y ejecuta el primer bloque catch cuyo tipo coincida, con el objeto lanzado en la variable: catch (Exception $e) { echo $e->getMessage(); }. Si no se lanza nada, los bloques catch se saltan.
¿Cómo capturo todas las excepciones y errores en PHP?
Captura Throwable: catch (Throwable $e). catch (Exception $e) solo captura excepciones, no los errores del motor como TypeError, ValueError o DivisionByZeroError, que extienden Error. Tanto Exception como Error implementan Throwable.
¿Cómo capturo varias excepciones en un solo bloque catch en PHP?
Separa los tipos con una barra vertical: catch (InvalidArgumentException | RangeException $e). También puedes escribir varios bloques catch seguidos; PHP usa el primero que coincide, así que pon primero los tipos más específicos.
¿finally se ejecuta si hay un return en try?
Sí. finally se ejecuta después de try y catch tanto si el código devolvió un valor como si lanzó una excepción o terminó con normalidad, incluso cuando try contiene return. Si el propio finally devuelve un valor, ese valor sustituye al de try, así que evita return dentro de finally.
¿try catch captura los warnings en PHP?
No. Los warnings y los notices (como leer una clave de array que no existe) no son excepciones, así que catch nunca los ve. Comprueba la condición antes de la llamada, usa el modo de excepciones de la función cuando lo tenga (json_decode(..., flags: JSON_THROW_ON_ERROR)) o convierte los warnings con set_error_handler() y ErrorException.