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