Jerarquía de excepciones personalizadas
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy — lección 67 de 91.
Si bien las clases de excepción integradas de PHP cubren escenarios comunes, las aplicaciones reales a menudo necesitan tipos de error más específicos. Crear una jerarquía de excepciones personalizada le permite definir excepciones que coincidan con su dominio y capturarlas en diferentes niveles de especificidad.
Para crear una excepción personalizada, simplemente extiende la clase Exception o una de sus subclases:
<?php
class PaymentException extends Exception {}
class InsufficientFundsException extends PaymentException {
public function __construct(
private float $required,
private float $available
) {
parent::__construct("Need $required but only $available available");
}
public function getShortfall(): float {
return $this->required - $this->available;
}
}
Observa cómo InsufficientFundsException extiende a PaymentException, no a Exception directamente. Esto crea una jerarquía en la que puedes capturar todas las excepciones relacionadas con pagos con un único bloque catch, o gestionar las específicas de forma individual.
Las excepciones personalizadas pueden incluir propiedades y métodos adicionales relevantes para el error. Aquí, getShortfall() proporciona información útil que una excepción genérica no podría ofrecer:
<?php
class Wallet {
public function __construct(private float $balance) {}
public function pay(float $amount): void {
if ($amount > $this->balance) {
throw new InsufficientFundsException($amount, $this->balance);
}
$this->balance -= $amount;
}
}
Una jerarquía de excepciones bien diseñada hace que su código sea más fácil de mantener. Al capturar excepciones, puede ser tan general o específico como sea necesario, y los propios objetos de excepción llevan un contexto significativo sobre lo que salió mal.
Desafío
FácilVamos a construir un sistema de procesamiento de pedidos con una jerarquía de excepciones personalizada que maneje diferentes tipos de fallos en los pedidos. Crearás excepciones que lleven un contexto significativo sobre lo que salió mal, haciendo que el manejo de errores sea más preciso e informativo.
Organizarás tu código en cuatro archivos:
OrderException.php— Crea una clase baseOrderExceptionque extienda deException. Esta servirá como la clase padre para todas las excepciones relacionadas con pedidos, permitiéndote capturar cualquier problema de pedido con un solo bloque catch cuando sea necesario.OutOfStockException.php— Crea una claseOutOfStockExceptionque extienda deOrderException. Esta excepción debe aceptar un nombre de producto y la cantidad solicitada en su constructor. Utiliza la promoción de propiedades del constructor para estas propiedades privadas. Llama al constructor padre con un mensaje en el formato"[productName] is out of stock (requested: [quantity])". Añade un métodogetProductName(): stringque devuelva el nombre del producto.InvalidQuantityException.php— Crea una claseInvalidQuantityExceptionque extienda deOrderException. Esta excepción debe aceptar un valor de cantidad en su constructor utilizando la promoción de propiedades del constructor como una propiedad privada. Llama al constructor padre con el mensaje"Invalid quantity: [quantity]". Añade un métodogetQuantity(): intque devuelva el valor de la cantidad.main.php— Incluye los tres archivos de excepciones. Recibirás dos entradas: un nombre de producto y una cantidad (como una cadena que necesita ser convertida a un entero).Basándote en el valor de la cantidad, lanza la excepción adecuada:
- Si la cantidad es menor que 1, lanza una
InvalidQuantityException - Si la cantidad es mayor que 100, lanza una
OutOfStockException(simulando stock insuficiente) - De lo contrario, imprime
"Order placed: [quantity]x [productName]"
Captura las excepciones utilizando la jerarquía. Primero captura
InvalidQuantityExceptione imprime"Quantity error: [message] (value: [quantity])"utilizando el mensaje de la excepción y el métodogetQuantity(). Luego capturaOutOfStockExceptione imprime"Stock error: [message] (product: [productName])"utilizando el mensaje de la excepción y el métodogetProductName().- Si la cantidad es menor que 1, lanza una
Esta jerarquía te permite manejar excepciones específicas individualmente mientras sigues teniendo la opción de capturar todos los problemas relacionados con pedidos a través de la clase base OrderException. Los métodos personalizados en cada excepción proporcionan un contexto que una excepción genérica no podría ofrecer.
Hoja de referencia
Crea excepciones personalizadas extendiendo la clase Exception o sus subclases:
<?php
class PaymentException extends Exception {}
class InsufficientFundsException extends PaymentException {
public function __construct(
private float $required,
private float $available
) {
parent::__construct("Need $required but only $available available");
}
public function getShortfall(): float {
return $this->required - $this->available;
}
}
Las excepciones personalizadas pueden formar una jerarquía al extender otras excepciones personalizadas. Esto permite capturar excepciones en diferentes niveles de especificidad: capturar todas las excepciones relacionadas con pagos con PaymentException, o manejar las específicas como InsufficientFundsException de forma individual.
Las excepciones personalizadas pueden incluir propiedades y métodos adicionales para proporcionar contexto sobre el error:
<?php
class Wallet {
public function __construct(private float $balance) {}
public function pay(float $amount): void {
if ($amount > $this->balance) {
throw new InsufficientFundsException($amount, $this->balance);
}
$this->balance -= $amount;
}
}
Usa la promoción de constructor para definir propiedades directamente en los parámetros del constructor. Llama a parent::__construct() para establecer el mensaje de la excepción.
Pruébalo tú mismo
<?php
// Incluir todos los archivos de excepciones
require_once 'OrderException.php';
require_once 'OutOfStockException.php';
require_once 'InvalidQuantityException.php';
// Leer entrada
$productName = trim(fgets(STDIN));
$quantity = intval(trim(fgets(STDIN)));
// TODO: Usar try-catch para manejar el procesamiento del pedido
// Dentro del bloque try:
// - Si quantity < 1, lanzar InvalidQuantityException
// - Si quantity > 100, lanzar OutOfStockException
// - De lo contrario, imprimir "Order placed: [quantity]x [productName]"
//
// Capturar InvalidQuantityException primero:
// - Imprimir "Quantity error: [message] (value: [quantity])" usando getMessage() y getQuantity()
//
// Capturar OutOfStockException:
// - Imprimir "Stock error: [message] (product: [productName])" usando getMessage() y getProductName()
?>Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a OOPClases vs ObjetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Resumen - Calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasResumen - Jerarquía de empleados7Encapsulamiento
Público, Protegido, PrivadoModificadores de acceso a fondoGetters y SettersOcultamiento de informaciónPromoción de constructor (8.0)Resumen - Sistema de registros de estudiantes10Conceptos avanzados de POO
Composición vs. HerenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibers (PHP 8.1)Clonación de objetos a fondoGeneradores e Iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Espacios de nombres y Autoloading
Introducción a los espacios de nombresLa palabra clave useEstándar de Autoloading PSR-4Autoloader de ComposerResumen - Proyecto organizado5Interfaces y Contratos
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesInterfaz vs Clase AbstractaType Hinting con InterfacesResumen - Calculadora de Formas8Métodos mágicos
Introducción a los métodos mágicos__toString y __debugInfo__get, __set, __isset, __unset__call y __callStatic__clone y clonación de objetos__serialize y __unserializeResumen - Colección personalizada11Sistema de tipos y manejo de errores
Declaraciones de tiposTipos anulablesTipos de unión e intersecciónClases de excepciónJerarquía de excepciones personalizadasTry, Catch, FinallyResumen - Validador de formularios3Propiedades de clase
Propiedades de instancia vs. estáticasConstantes en clasesMétodos y propiedades estáticosPropiedades privadas y protegidasPropiedades de solo lectura (PHP 8.1)Resumen - Gestor de cuenta bancaria6Polimorfismo
Sobrescritura de métodos revisadaPolimorfismo mediante interfacesType Hinting y tipos de uniónEnlace estático tardíoResumen - Procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos de TraitsMétodos abstractos en TraitsTraits frente a herencia12Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy