I'm trying to found the best way to manage Exception for all over my application.
Actually, I've come with two solutions :
Solution 1
One Exception to govern them all.
namespace App\Utils\Exception; class LogicException extends Exception { /** * @param MessageI18N $messageI18N A message which may contain parameters for translation. * @param string|null $redirectRoute [optional] Route to redirect after exception have been throw. * @param string $status [optional] A status for alert msg (default is {@see eNotificationStatus::ERROR}). * @inheritDoc */ public function __construct(MessageI18N $messageI18N, ?string $redirectRoute = null, string $status = eNotificationStatus::ERROR, ?int $code = 0, Throwable $previous = null){ parent::__construct($messageI18N->getMessage(), $code, $previous); $this->_redirectRoute = $redirectRoute; $this->_status = $status; $this->_messageDatas = $messageI18N->getParameters(); } } namespace App\Utils\Exception; class MessageI18N { private string $_message; private array $_parameters; public function __construct(string $message, array $parameters = array()) { $this->_message = $message; $this->_parameters = $parameters; } ... } Now I can use this LogicException class everywhere I need it as :
namespace App\Services\Domain\Email; class EmailService implements EmailServiceInterface { ... public function sendEmailResetPassword(string $email): void { try { $this->_mailer->send($email); } catch (TransportExceptionInterface $e) { throw new LogicException( new MessageI18N( "an_error_occurs.when_sending_email_to_{email}", ['{email}' => $email] ), 'send_mail_password' // <-- route to redirect ); } } } namespace App\Controller; class EmailController extends _BaseController { ... /** * @Route("email/send/password/forgot/", name="email_send_password_forgot") * ... */ public function sendEmailResetPassword(Request $request): void { ... // Get email from request query try{ $this->_emailService->sendEmailResetPassword($email); $this->addFlash( // Success message "an_email_have_been_sent_to_{email}_(check_your_spams)", ['{email}' => $email]) ); }catch(LogicException $e){ $this->addFlash( // Error message $e->getStatus, // Status may be warning or error $this->_translator->trans( $e->getMessage, $e->getgetMessageDatas() ) ); } return $this->render('mail_password_forgot_form', ['last_mail' => $email]); } } Advantages
- Easy to handle exception
- Easy to provide extra parameters for I18N purpose
- Messages can be personalise at Service layer, where it have all the specific knowledge of why error occurs.
- The route to redirect can be personalise too.
Disadvantages
- The exception logic take more space than service logic make it hard to read.
- The way Exception class is extended look like a terrible mess, as an object is provided, but actualy isn't a property of LogicException. IDK why but it's look like so horrible to me.
- If LogicException need to be change, there might be impact in all place where it is use. A lot of dependecy to this class may require a lot of change !
Solution 2
Solution 2 is about creating a new Exception class for each logic exception wich may occurs.
class EmailInvalidFormat extends \LogicException // <-- SPL\LogicException{ } class UserNotFoundException extends \LogicException // <-- SPL\LogicException{ } class UserNameMustContainOnlyAlphanumeric extends \UnexpectedValueException// <-- SPL\UnexpectedValueException{ } class UserNameLength extends \LengthException// <-- SPL\LengthException{ } class UserDupplicateName extends \LogicException// <-- SPL\LogicException{ } ... I let you imagine that i'll probably break some records if I create an exception by problem. I thin you guess that for every Exception class I'll display a specific translation message wich would've been handle by the controller. Service layer only throw Exception class without knowing anything about the message.
Advantages
- More readable code.
- Everything related to translation is inside of the controller.
- If an exception class need to be rename, this don't have a lot of impacts.
Disadvantages
- Less flexibility about message, as controller may not have a lot of informations about each exception.
- The message will be more generic.
- Controller got too much responsibility as he's got to manage every exception that'll be thown.
- Code can't get reuse through other application as Exception may be related to Application logic