Todo programa falla en algún momento: la base de datos no responde, una API tarda demasiado, el usuario envía algo inesperado. La diferencia entre una aplicación amateur y una profesional no es que falle menos, sino cómo falla: si muestra al visitante la ruta de tus archivos y tu contraseña de la base de datos, o una página amable mientras tú recibes el detalle en un log. En el libro PHP desde cero hasta experto el manejo de errores tiene su propio capítulo; esta es una guía práctica.
Errores vs excepciones
- Errores clásicos (avisos y advertencias): PHP los emite cuando algo va mal pero puede seguir, por ejemplo al leer una clave de array que no existe.
- Excepciones: objetos que se “lanzan” con
throwy detienen la ejecución hasta que alguien los captura concatch.
Desde PHP 7, la mayoría de los errores graves (llamar a una función que no existe, un tipo incorrecto con
strict_types) son objetos Error, y tanto Error como
Exception implementan la interfaz Throwable.
Configuración: desarrollo vs producción
| Directiva de php.ini | Desarrollo | Producción |
|---|---|---|
display_errors | On | Off |
log_errors | On | On |
error_reporting | E_ALL | E_ALL |
error_log | Opcional | Ruta a un archivo fuera de la carpeta pública |
Fíjate en que error_reporting es E_ALL en ambos casos: en producción quieres
registrar todo, solo que sin mostrarlo. Si no puedes editar php.ini en tu
hosting, puedes fijarlo al inicio de la aplicación:
<?php
$esProduccion = getenv('APP_ENV') === 'production';
error_reporting(E_ALL);
ini_set('display_errors', $esProduccion ? '0' : '1');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/../logs/php-errores.log');
try, catch y finally
<?php
function leerConfiguracion(string $ruta): array
{
if (!is_readable($ruta)) {
throw new RuntimeException("No se puede leer la configuración: $ruta");
}
return json_decode(file_get_contents($ruta), true, flags: JSON_THROW_ON_ERROR);
}
try {
$config = leerConfiguracion(__DIR__ . '/config.json');
} catch (JsonException $e) {
// el archivo existe pero el JSON está mal escrito
error_log('config.json inválido: ' . $e->getMessage());
$config = [];
} catch (RuntimeException $e) {
error_log($e->getMessage());
$config = [];
} finally {
// se ejecuta siempre, haya excepción o no: cerrar archivos, liberar bloqueos...
}
Los bloques catch se evalúan en orden, así que pon primero las excepciones más específicas.
JsonException va antes que RuntimeException porque es más concreta.
Excepciones propias
Crear tus propias clases de excepción permite distinguir los errores de tu dominio (“stock insuficiente”) de los técnicos (“la base de datos no responde”) y tratarlos de forma distinta:
<?php
declare(strict_types=1);
// Errores que se pueden mostrar al usuario tal cual
class ErrorDeValidacion extends Exception {}
class StockInsuficiente extends ErrorDeValidacion
{
public function __construct(string $producto, int $disponible)
{
parent::__construct("Solo quedan $disponible unidades de $producto.");
}
}
function reservar(string $producto, int $cantidad, int $stock): void
{
if ($cantidad > $stock) {
throw new StockInsuficiente($producto, $stock);
}
// ...
}
try {
reservar('Teclado', 5, 3);
} catch (ErrorDeValidacion $e) {
echo $e->getMessage(); // "Solo quedan 3 unidades de Teclado."
}
Un manejador global
Aunque captures lo previsible, siempre habrá algo que se escape. Un manejador global se asegura de que, pase lo que pase, el usuario ve una página digna y tú tienes el detalle en el log:
<?php
// Convierte avisos y advertencias en excepciones, para tratarlos igual
set_error_handler(function (int $nivel, string $mensaje, string $archivo, int $linea): bool {
if (!(error_reporting() & $nivel)) {
return false; // error silenciado con @: dejar que PHP lo ignore
}
throw new ErrorException($mensaje, 0, $nivel, $archivo, $linea);
});
// Última red de seguridad: cualquier Throwable no capturado
set_exception_handler(function (Throwable $e): void {
$id = bin2hex(random_bytes(4)); // referencia para cruzar con el log
error_log(sprintf('[%s] %s en %s:%d', $id, $e->getMessage(), $e->getFile(), $e->getLine()));
http_response_code(500);
echo "Algo salió mal. Si el problema continúa, indícanos el código $id.";
});
Incluye este archivo al principio de tu aplicación, junto con la configuración de errores. El código de referencia permite que un usuario te reporte un fallo y lo encuentres en el log en segundos.
Antipatrones
- El
catchvacío:catch (Exception $e) {}esconde el problema y lo hace aparecer más tarde, en otro sitio y sin pistas. - El operador
@: silenciar errores con@funcion()te impide saber por qué algo no funciona. - Mostrar
$e->getMessage()de errores técnicos al usuario: puede revelar rutas, consultas SQL o credenciales. - Capturar en todas partes: captura solo donde puedas hacer algo útil (reintentar, usar un valor por defecto, mostrar un mensaje). El resto, que suba al manejador global.
- Un log que nadie mira: revisa los logs con regularidad o configura alertas para los errores graves.
Estos patrones encajan con el resto de la serie: la función de consumo de APIs y la de subida de imágenes lanzan excepciones precisamente para que puedas manejarlas así.
Aplicaciones que fallan con elegancia
El libro PHP desde cero hasta experto dedica un capítulo al manejo de errores, clave para llevar tus proyectos prácticos a producción.
Comprar en Amazon