Search code examples
phpexceptionsymfony4http-status-code-500

Symfony 4, how return properly a error 500 server from a service Class


From a Service class, how manage exceptions and return an error 500 ? By example, I have a service class 'A' called from another service Class 'B'. The content of the service 'A' is :

namespace App\Service;

use ...

class A
{
    ...
    public static function foo(){

        $tmp =  [];

        // do some stuff

        if(isOK($tmp)){
            return $tmp;        
        }else{
            // return 500 with message
        }
    }

    private static function isOK($tmp){
        // do some stuff
    }
}

I tried this :

namespace App\Service;

use ...

class A
{
    ...
    public static function foo(){

        $tmp =  [];

        // do some stuff

        if(isOK($tmp)){
            return $tmp;        
        }else{
            // return 500 with message
        }
    }

    private static function isOK($tmp){
        try{
            if(...) throw new \Exception();
        }catch (HttpException $e){
            dump('not valid data $tmp var in ' . __FUNCTION__,500);
            exit;
        }
    }
}

But I don't think I use a good way. If I deliberately set a wrong value to the $tmp var, the process is stopped (as I want) and, in a case where I use this service for build a symfony http web page, a blank page is displayed with my message but this page get a status 200 (not a 500 'internal server error').

What is the good/properly way for return an exception from a Service ?

Is there a global (symfony ? oop php?) way for manage properly errors exceptions in the 'service called from another service' context and/or in the 'service called from a controller used only for REST web service' context and/or , more conventionally, in the 'service called from a classical http controller' context ? (bonus : and/or in the "service called from a custom Command Class")


Solution

  • Maybe I completely misunderstand the question, but I'd say: throw an Exception from your Service.

    But: you only catch an Exception, if you can properly handle it. In your case it looks as if you can't handle it in your Service, so you let it bubble up its way to the appropriate Symfony component (that differs between Console command, Controller or Rest endpoint).

    The Service shouldn't set the 500 code, as it doesn't know in which context it is used. Therefor you might want to throw an explicit ServiceException and catch that in your controller and convert it to something more useful:

    class A
    {
        public function foo(){
            $tmp =  [];
            if($this->isOK($tmp)){
                return $tmp;        
            }
            throw new ServiceException('Failed checking $tmp');
        }
    
        private function isOK($tmp){
            return false;
        }
    }
    
    class TestController
    {
        /**
         * @var A
         */
        protected $a;
    
        public function fooAction() {
            try {
                $this->a->foo();
            } catch (ServiceException $e) {
                throw new HttpException(500, $e->getMessage())
            }
        }
    }
    

    For web and rest you have to make sure that your Exception has the correct code, which will then be used to set the HTTP code.

    Only the code that uses the service knows how to handle the Exception properly. As the status code doesn't matter in your console command, you could not catch it. But in general you can say that this is not best practice, as you might have to do some cleanup (close connections, close file handles, write error log) before the Exception is passed to the next code level.

    An example for the console:

    class MyCommand extends Command
    {
        protected function execute(InputInterface $input, OutputInterface $output)
        {
            $io = new SymfonyStyle($input, $output);
            $a = new A();
            try {
                $this->a->foo();
            } catch (ServiceException $e) {
                // write to log file
                $io->error('Service failed: ' . $e->getMessage());
                return;
            }
            // do more stuff
        }
    }