Search code examples
laravelexceptionlaravel-controller

Handle Exception From Within Method


I am implementing payments for my website using the API of an external service (ie. the service of the payment provider).

Let's say the user clicks 'BUY', and then we go to my controller which says something along the lines of:

public function buyFunction() {

    $result = $this->ExternalService->pay();

    if ($result->success == true) {
        return 'We are happy';
    }
}

I have also created the aforementioned externalService which has the pay() method:

class ExternalService {

    public function pay() {
        response = //Do stuff with Guzzle to call the API to make the payment

        return response;
    }
}

Now, sometimes things go wrong. Let's say the API returns an error - which means that it throws a GuzzleException - how do I handle that?

Ideally, if there is an error, I would like to log it and redirect the user to a page and tell him that something went wrong.

What I've tried

  1. I have tried using a try/catch statement within the pay() function and using abort(500) but this doesn't allow me to redirect to the page I want to.

  2. I have tried using a try/catch statement within the pay() function and using return redirect('/mypage') but this just returns a Redirect object to the controller, which then fails when it tries to call result->success

  3. I have tried using number 2 but also adding a try/catch block to the controller method, but nothing changed.

In the end, I have found two solutions. In both, I use a try/catch block inside the pay() method. Then I either return 0; and check in the controller if (result == 0) or I use abort( redirect('/mypage') ); inside the try/catch block of the pay() method.

What is the right way to handle this? How to use the try/catch blocks?


Solution

  • In my experience, avoid handling exceptions let them pass through and handle them accordingly with try catches. This is the most pragmatic approach. Alternatively you will end up checking result is correct in weird places, eg. if ($result) {...}. Just assume it went good, except if the exception is thrown. Bonus: never do Pokemon catches with Exception $e unless you specifically needs it!

    class ExternalService {
    
        public function pay() {
            try {
                response = $client->get(...);
            } catch (BadResponseException $exception) {
                Log::warning('This should not happen check payment api: ' . $exception->getMessage());
                
                throw new PaymentException('Payment did not go through');
            } 
    
            return response;
        }
    }
    

    Assuming you have your own Exception.

    class PaymentException extends HttpException
    {
        public function __construct(?\Exception $previous = null)
        {
            parent::__construct(Response::HTTP_BAD_REQUEST, 'Unexpected error processing the payment', $previous);
        }
    }
    

    This enables you to handle the flow in a controller, where it would make sense to handle the redirect. Sometimes if the exception is very integral or common to the web app, it can also be handled by the exception handler instead.

    class PaymentController {
        public function pay(PaymentService $service) {
            try {
                $payment = $service->buyFunction();
            } catch (PaymentException $exception) {
                return redirect()->route('app.payment.error');
            } 
            
            return view('app.payment.success', compact('payment'));
        }
    }