Question

Disclaimer : I come on this topic after it has been recommended to me on my previous closed SO topic (see closed one : https://stackoverflow.com/questions/64338968/how-to-effectively-manage-a-large-number-of-exceptions-i18n-problematic).

Also, you'll see that even after a doing some research and reading about the subject, I fail to found any consensual answer to this problematic. That's what I'm looking for today.

So to begin I've been told that handling user inputs error by stoping work flow throwing Exception is a (very) bad idea.

Some references to these topics :

So Even though there's a lot of debate around this question, I've make my opinion to not use Exception to stop propagation and take control over the work flow.

First I was thinking that it's a standard way to proceed. Throwing an exception to stop the process and let the controller decide the message to display. It was even possible to filter Exception by using inheritance.

I was even comforted in this idea by the "Fail early" principle. So basically I create all of my service arround this concept of throwing exception if a problem occurs. And then my controller catch my personnal exception (like LogicException) and display it nicely to the view like :

namespace App\Controller;

class ProductController{
   private ProductServiceInterface $productService;
   public function __construct(ProductServiceInterface $productService){
      $this->_productService = $productService;
   }
   
   public function show($id){
      try{
         $product = $this->_productService->getProductById($id);
      }catch(_LogicException $e){
         $this->addFlash($e->getStatus, $e->getMessage());
         if ($e->getRedirectRoute()) return $this->redirectToRoute($e->getRedirectRoute());
      }
   }
}

But the more I use this pattern, the more I see it looks wrong in many way :

  • Controller have responsibility to handle exceptions and returning a Response to a request (Breaking single responsibility principle).
  • Service should have the responsibility to handle exception, as they got some usefull context to provide to exceptions.
  • Keeps the controller as simple as possible ! (One of the best topics about this subject)
  • I have to dupplicate the handing exception logic on every function, for every controller. And dupplicate code is never a good idea. Even though I could just extends all controller on a base class, I still have to surround with try/catch on every functions for every controller. And if I change the exception handling logic I have dependecy on every Controller so I have to change it everywhere arround the application. (Encapsulate What May Change).

I think that many of tutorial propose as an exercice to try/catch incorrect user input so it may lead many misguided peoples (like me) to think that Exception can be use to control wrok flow when a user input is incorrect. Always following the "Fail early" principles, it seams easy to stop the process with an exception, and so I adopt it as a standard way to proceed.

Of course, if you encouter critical error (database connection unavailable, mailer dsn unavailable...), you still have to throw an exception. This is especially true for technical errors. You should've two different behavior, one in debug mode so Exception is thrown and you can debug it easily. And a no debug mode (for production environment...) where technical Exception are stored in a log file, and then a user friendly message display "Something went wrong" and for instance will proceed to redirect user to security.

So finally, I'm going to use a solution based on this solution : https://gist.github.com/javiereguiluz/a66f084a4f2cf11cf0ee#gistcomment-1308966

This is a verry elegant solution to me. Using a event dispatcher and a event listener have a lot of advantages :

  • Giving the responsibility to handle error message to a Event listener.
  • Can access to context through the Event object passed to dispatcher.
  • Can use inheritance to have translation logic inside one EventHelper class.
  • All error message in one place. If a message is needed to be change, there is no need to search for it everywhere.

Tutorial about event listener

What do you think about this solution, please let me know.

Was it helpful?

Solution

in my opinion, you are looking at some old discussions (i.e. symfony 2.x) while symfony has evolved to version 5 (some concepts may stay valid, though implementation changed).

Try to dig into newer code, as I think that many PHP Frameworks evolved in the last years.

For example the Symfony 5 Validator (also usable as a stand alone component) provides tools to validate values following the JSR-303 Bean Validation specification.

That means that it will execute all validation rules and you will get a list of violations.

Then you can turn that list into a nice error message, that shows every violation ( not only the first one, as it would be with exceptions )

example from above link:

$validator = Validation::createValidator();
$violations = $validator->validate('Bernhard', [
    new Length(['min' => 10]),
    new NotBlank(),
]);

if (0 !== count($violations)) {
    // there are errors, now you can show them
    foreach ($violations as $violation) {
        echo $violation->getMessage().'<br>';
    }
}
Licensed under: CC-BY-SA with attribution
scroll top