Curso de PHP
Excepciones en PHP: try, catch, finally y throw
Por Víctor Peña · Publicado el

Hola, ¿cómo están? Continuando con el curso de PHP, hoy veremos las excepciones: la forma moderna de manejar situaciones donde algo sale mal.
¡Empecemos!
Qué es una excepción
Una excepción es un objeto que representa un error o una situación anormal durante la ejecución. Cuando se lanza, el flujo normal del programa se interrumpe y salta al lugar preparado para atenderla.
La diferencia con devolver false o null es importante: una excepción no se puede ignorar en silencio. Si nadie la atiende, el programa se detiene y te enteras.
El problema de no usarlas
<?php
function dividir(float $a, float $b): float
{
if ($b === 0.0) {
return 0; // ¿resultado válido o error?
}
return $a / $b;
}
$resultado = dividir(10, 0); // 0, pero ¿por qué?
Quien recibe ese 0 no sabe si es el resultado correcto o un error disfrazado. Y si olvida comprobarlo, el error se propaga silenciosamente.
Lanzar una excepción con throw
<?php
declare(strict_types=1);
function dividir(float $a, float $b): float
{
if ($b === 0.0) {
throw new InvalidArgumentException("No se puede dividir entre cero");
}
return $a / $b;
}
Ahora la función no puede devolver un resultado incorrecto: o divide bien, o lanza.
Capturar con try catch
<?php
try {
$resultado = dividir(10, 0);
echo $resultado;
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage();
}
trycontiene el código que puede fallar.catchrecibe la excepción y decide qué hacer.
Si no ocurre nada, el catch se ignora por completo.
El bloque finally
finally se ejecuta siempre, haya error o no:
<?php
$archivo = fopen("datos.txt", "r");
try {
// procesar el archivo
throw new RuntimeException("Algo falló");
} catch (RuntimeException $e) {
echo "Error: " . $e->getMessage();
} finally {
fclose($archivo); // se cierra pase lo que pase
}
Es el lugar para liberar recursos: cerrar archivos, cerrar conexiones, revertir una transacción.
Los métodos de una excepción
Todas las excepciones traen información útil:
<?php
try {
throw new RuntimeException("Falló la conexión", 500);
} catch (RuntimeException $e) {
echo $e->getMessage(); // Falló la conexión
echo $e->getCode(); // 500
echo $e->getFile(); // el archivo donde ocurrió
echo $e->getLine(); // la línea
echo $e->getTraceAsString(); // la pila de llamadas
}
getTraceAsString() es lo que te dice cómo se llegó hasta el error, y suele ser lo más valioso al depurar.
La jerarquía de excepciones
PHP organiza los errores en una jerarquía. Conocerla te permite capturar con la precisión adecuada:
Throwable
├── Error ← errores del propio PHP
│ ├── TypeError
│ ├── ArgumentCountError
│ └── DivisionByZeroError
└── Exception ← excepciones de la aplicación
├── RuntimeException
│ ├── OutOfBoundsException
│ └── UnexpectedValueException
├── LogicException
│ ├── InvalidArgumentException
│ └── DomainException
└── JsonException
La distinción de fondo:
LogicExceptiony sus hijas: errores del programador. No debieron ocurrir; se arreglan cambiando el código.RuntimeExceptiony sus hijas: errores que dependen de circunstancias. Un archivo que no existe, una conexión caída.
Error y Exception son ramas distintas. Por eso catch (Exception $e) no captura un TypeError. Si necesitas capturar cualquier cosa:
<?php
try {
// ...
} catch (Throwable $e) {
// atrapa absolutamente todo
}
Capturar varios tipos
El orden importa: de lo más específico a lo más general.
<?php
try {
procesarPedido($datos);
} catch (InvalidArgumentException $e) {
echo "Datos incorrectos: " . $e->getMessage();
} catch (RuntimeException $e) {
echo "Error de ejecución: " . $e->getMessage();
} catch (Throwable $e) {
echo "Error inesperado";
}
Si pusieras Throwable primero, capturaría todo y los demás nunca se ejecutarían.
También puedes agrupar tipos con |:
<?php
try {
// ...
} catch (InvalidArgumentException | DomainException $e) {
echo "Problema con los datos";
}
Crear excepciones personalizadas
Es una práctica muy recomendable: excepciones con nombre propio que describen problemas de tu dominio.
<?php
class SaldoInsuficienteException extends RuntimeException
{
public function __construct(
private float $saldoActual,
private float $montoSolicitado,
) {
parent::__construct(
sprintf(
"Saldo insuficiente: disponible %.2f, solicitado %.2f",
$saldoActual,
$montoSolicitado
)
);
}
public function getFaltante(): float
{
return $this->montoSolicitado - $this->saldoActual;
}
}
Y al usarla:
<?php
try {
$cuenta->retirar(5000);
} catch (SaldoInsuficienteException $e) {
echo $e->getMessage();
echo "Te faltan " . $e->getFaltante();
}
La ventaja es doble: el catch es específico —no atrapas errores que no te tocan— y la excepción puede llevar datos útiles además del mensaje.
Relanzar y encadenar
A veces capturas una excepción para añadir contexto y volver a lanzarla:
<?php
try {
$this->conectar();
} catch (PDOException $e) {
throw new RuntimeException(
"No se pudo iniciar el reporte mensual",
0,
$e // la excepción original
);
}
El tercer parámetro conserva la excepción original, accesible con getPrevious(). Así el mensaje es comprensible para quien lo lee y no pierdes el detalle técnico.
Cuándo usar excepciones y cuándo no
Aquí hay un equilibrio que conviene tener claro.
Sí, lanza excepción cuando:
- Los datos recibidos son inválidos.
- Un recurso necesario no está disponible.
- Se viola una regla del negocio.
- El método no puede cumplir lo que promete.
No uses excepciones para:
- Flujo normal del programa. Que una búsqueda no encuentre resultados no es excepcional: devuelve un array vacío.
- Validación de formularios. Ahí quieres recoger todos los errores para mostrarlos juntos, no detenerte en el primero.
La regla: una excepción es para lo excepcional, no para lo previsible.
Un ejemplo práctico
<?php
declare(strict_types=1);
class PedidoNoEncontradoException extends RuntimeException {}
class PedidoYaPagadoException extends RuntimeException {}
class ServicioDePagos
{
private array $pedidos = [
1 => ['total' => 450.0, 'estado' => 'pendiente'],
2 => ['total' => 300.0, 'estado' => 'pagado'],
];
public function pagar(int $id, float $monto): string
{
if (!isset($this->pedidos[$id])) {
throw new PedidoNoEncontradoException("No existe el pedido $id");
}
$pedido = $this->pedidos[$id];
if ($pedido['estado'] === 'pagado') {
throw new PedidoYaPagadoException("El pedido $id ya fue pagado");
}
if ($monto < $pedido['total']) {
throw new InvalidArgumentException(
sprintf("Monto insuficiente: faltan %.2f", $pedido['total'] - $monto)
);
}
return "Pedido $id pagado correctamente";
}
}
$servicio = new ServicioDePagos();
foreach ([[1, 450.0], [2, 300.0], [99, 100.0], [1, 50.0]] as [$id, $monto]) {
try {
echo $servicio->pagar($id, $monto) . "\n";
} catch (PedidoNoEncontradoException $e) {
echo "404: " . $e->getMessage() . "\n";
} catch (PedidoYaPagadoException $e) {
echo "Conflicto: " . $e->getMessage() . "\n";
} catch (InvalidArgumentException $e) {
echo "Datos: " . $e->getMessage() . "\n";
}
}
Cada tipo de problema se atiende de forma distinta, y el código del método principal se lee limpio: valida, lanza y sigue.
Errores comunes
- Capturar y no hacer nada. Un
catchvacío esconde el problema y complica muchísimo la depuración. - Capturar
Throwablede entrada. Atrapas incluso errores de programación que deberías ver. - Mostrar
getMessage()al usuario final. Puede revelar rutas y detalles internos. Registra el detalle, muestra un mensaje genérico. - Usar excepciones para el flujo normal.
- Poner el
catchgeneral antes que los específicos.
Para cerrar
Las excepciones convierten los errores silenciosos en errores visibles, y eso siempre es mejor. Lo importante: lanza cuando tu método no pueda cumplir lo que promete, captura solo lo que sepas atender, y usa finally para liberar recursos.
En la siguiente lección veremos los atributos y métodos estáticos.
Saludos y éxitos.
Norvic Software
Desarrollamos el software que tu empresa necesita
Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.
- Chatbots con Inteligencia Artificial
- Creación de agentes de IA
- Integración de IA en tus sistemas
- Desarrollo de software a medida
- Aplicaciones móviles iOS y Android
- Consultoría y asesoramiento técnico
Cotización sin costo · Respuesta directa por WhatsApp