Menu

Excepciones en PHP: throw y clases de excepción propias

En PHP lanzas una excepción con throw new Exception('message');, y quien llama la lee con $e->getMessage() en un bloque catch. Aprende las clases de excepción nativas, a escribir excepciones propias, getCode(), getLine() y getPrevious(), throw como expresión y los errores que lanza el propio PHP, como ValueError y TypeError.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

En PHP lanzas una excepción con throw new Exception('message');. La ejecución se detiene en esa línea y salta al bloque catch más cercano que acepte el tipo de la excepción, donde $e->getMessage() devuelve el mensaje. Usa una clase específica, como InvalidArgumentException, para que quienes llaman puedan distinguir los problemas.

Una excepción es un objeto. throw lo pasa hacia arriba por la pila de llamadas hasta el primer llamador con un catch que coincida, y ese llamador decide qué hacer; la función que encontró el problema no tiene por qué saberlo. La mecánica de la captura, finally y capturar varios tipos está en la página de try catch.

Qué contiene un objeto de excepción

Toda excepción lleva un mensaje, un código entero, el archivo y la línea donde se creó, y una traza de la pila. El constructor recibe ($message, $code, $previous), todos opcionales:

getLine() es la línea del throw new, no la del catch. La traza lista las llamadas que llevaron hasta ahí (#0 /home/index.php(8): findOrder()), que suele ser lo primero que hay que leer cuando aparece un error en un log. Convertir la excepción en cadena (echo $e; o (string) $e) lo imprime todo en el formato estándar de PHP.

Clases de excepción nativas

PHP trae una familia de clases de excepción en su Standard PHP Library (SPL). Lanzar la que nombra el problema hace que tu código se lea mejor y permite a quienes llaman capturar de forma precisa. Todas estas extienden Exception:

ClaseLánzala cuando
InvalidArgumentExceptionun argumento tiene una forma incorrecta: un nombre vacío, una opción desconocida
DomainExceptionun valor está fuera del conjunto que tiene sentido: un precio negativo
OutOfRangeExceptionel código pidió un índice que nunca puede existir (un bug en quien llama)
OutOfBoundsExceptionfalta una clave o un índice en datos que solo se conocen en ejecución
RangeExceptionun resultado calculado queda fuera del rango válido durante la ejecución
LengthExceptionalgo es demasiado largo o demasiado corto
RuntimeExceptionun problema que solo se detecta en ejecución: un disco lleno, un timeout
UnexpectedValueExceptionuna función devolvió o recibió un valor de un tipo que no esperabas
LogicExceptionel propio código está mal, un bug y no una entrada incorrecta
JsonExceptionla lanzan json_encode()/json_decode() con JSON_THROW_ON_ERROR

InvalidArgumentException, DomainException, LengthException y OutOfRangeException extienden LogicException; OutOfBoundsException, RangeException y UnexpectedValueException extienden RuntimeException. Puedes comprobar tú mismo los padres de cualquier clase:

TypeError tiene como padre Error, no Exception: pertenece a la segunda familia, la de abajo.

Errores que lanza el propio PHP

Desde PHP 7, y mucho más desde PHP 8, las funciones y los operadores del propio PHP lanzan subclases de Error para problemas que antes eran warnings. Los capturas de la misma forma, pero catch (Exception $e) no coincide con ellos:

DivisionByZeroError, TypeError, ValueError, ArgumentCountError, UnhandledMatchError y el Error simple extienden todos Error. Cada uno indica más a menudo un bug en el código que llama que datos incorrectos, y por eso viven fuera del árbol de Exception. declare(strict_types=1) es lo que hace de str_repeat(5, 2) un TypeError; sin él, PHP convertiría 5 en "5".

Escribir una clase de excepción propia

Una subclase de una línea suele ser todo lo que necesitas: el propio nombre de la clase lleva el significado, y quienes llaman pueden capturar justo ese tipo. Cuando quien captura necesita datos (qué pedido, qué importe), añade propiedades y pasa el mensaje hacia arriba con parent::__construct():

Elige el padre con cuidado: extender RuntimeException (en lugar de Exception a secas) significa que el código que captura RuntimeException también maneja la tuya. Una jerarquía pequeña, una excepción base por librería o módulo con subclases específicas debajo, permite a quienes llaman elegir entre "capturar todo lo de pagos" y "capturar solo este caso".

Encadenar excepciones con previous

Cuando capturas una excepción de bajo nivel y lanzas una de más alto nivel, pasa la original como tercer argumento. No se pierde nada: getPrevious() recorre el camino hasta la causa raíz.

El usuario ve "customers.csv could not be imported"; el log, al recorrer la cadena, recibe además "row 2 has 2 columns". Si una excepción encadenada nunca se captura, el error fatal de PHP imprime toda la cadena: primero la causa original y luego cada envoltorio bajo una línea Next.

throw como expresión

Desde PHP 8.0, throw es una expresión, así que puede ir donde se espera un valor: después de ??, en un ternario, en una función flecha. Convierte "obtén esto o falla" en una sola línea:

Cuándo no lanzar una excepción

Las excepciones son para situaciones que la función actual no puede manejar y que el flujo normal no espera. Una búsqueda que no encuentra nada, un campo opcional vacío o un usuario que no ha iniciado sesión son resultados normales: devuelve null, false o un array vacío y deja que quien llama lo compruebe, y reserva las excepciones para los casos en los que continuar sería un error.

Dos hábitos más mantienen legible el código con excepciones: nunca captures una excepción solo para ignorarla (un catch (Exception $e) {} vacío esconde el siguiente bug real), y lanza clases específicas en lugar de una Exception a secas, para que quien llama nunca se vea obligado a capturarlo todo para manejar un caso.

Preguntas frecuentes

¿Cómo lanzo una excepción en PHP?

Crea un objeto de excepción y lánzalo: throw new InvalidArgumentException('Quantity must be positive');. La ejecución se detiene en esa línea y salta al bloque catch correspondiente más cercano; si no hay ninguno, el script termina con un error fatal "Uncaught".

¿Cómo creo una excepción propia en PHP?

Extiende Exception o una de sus subclases: class PaymentFailedException extends RuntimeException {}. Esa línea basta para lanzarla y capturarla por su propio tipo. Añade propiedades y un constructor cuando quien captura necesite datos extra, y llama a parent::__construct($message, $code, $previous).

¿Cuál es la diferencia entre getMessage y getCode?

getMessage() devuelve el texto pasado como primer argumento del constructor, pensado para personas y logs. getCode() devuelve el entero pasado como segundo argumento (0 por defecto), útil para comprobaciones automáticas como asignar errores a códigos de estado HTTP.

¿Qué excepción debo lanzar en PHP?

Usa una clase nativa de SPL que coincida con el problema: InvalidArgumentException para un argumento incorrecto, DomainException para un valor fuera de lo permitido, RuntimeException para fallos que solo se ven en ejecución (un disco lleno, un timeout), LogicException para errores de programación. Para errores que quienes llaman deben distinguir, crea tu propia subclase.

¿Qué es el encadenamiento de excepciones en PHP?

Pasar la excepción original como tercer argumento del constructor al lanzar una nueva: throw new ImportException('Import failed', 0, $e);. La nueva excepción aporta contexto, y $e->getPrevious() sobre ella devuelve la causa original, así que no se pierde ningún detalle.

Ilustración de los lenguajes de programación de Coddy

Aprende a programar con Coddy

COMENZAR