Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

connecteev's avatar

Proper way to handle Exceptions

What I am trying to do is build a Stripe-specific exception class to handle any Stripe-related exception:

        try {
            $stripeCustomer = $user->asStripeCustomer();
            $defaultPaymentMethodId = $stripeCustomer->invoice_settings->default_payment_method;
            return $defaultPaymentMethodId;

        } catch(\Stripe\Exception\CardException $e) {
            // Since it's a decline, \Stripe\Exception\CardException will be caught
            echo 'Status is:' . $e->getHttpStatus() . '\n';
            echo 'Type is:' . $e->getError()->type . '\n';
            echo 'Code is:' . $e->getError()->code . '\n';
            // param is '' in this case
            echo 'Param is:' . $e->getError()->param . '\n';
            echo 'Message is:' . $e->getError()->message . '\n';
        } catch (\Stripe\Exception\RateLimitException $e) {
            // Too many requests made to the API too quickly
        } catch (\Stripe\Exception\InvalidRequestException $e) {
            // Invalid parameters were supplied to Stripe's API
        } catch (\Stripe\Exception\AuthenticationException $e) {
            // Authentication with Stripe's API failed
            // (maybe you changed API keys recently)
        } catch (\Stripe\Exception\ApiConnectionException $e) {
            // Network communication with Stripe failed
        } catch (\Stripe\Exception\ApiErrorException $e) {
            // Display a very generic error to the user, and maybe send
            // yourself an email
        } catch (\Exception $e) {
            // Something else happened, completely unrelated to Stripe
            return [false, '$e->getError()->message', null];
        }

I would like to move all this exception logic (for Stripe) from my Controller above into an exception class.

What is the correct (Laravel) way to handle exceptions? How would you do it?

Some context:

When Laravel 7.x came out, according to the upgrade guide I believe there was a symfony-related update on how exceptions were handled.

Not sure if the php artisan make:exception command has been updated, but running php artisan make:exception MyStripeException generates a file like this

<?php

namespace App\Exceptions;

use Exception;

class MyStripeException extends Exception
{
    //
}

However, if you look at app\Exceptions\Handler.php it looks more like this


<?php

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that are not reported.
     *
     * @var array
     */
    protected $dontReport = [
        //
    ];

    /**
     * A list of the inputs that are never flashed for validation exceptions.
     *
     * @var array
     */
    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];

    /**
     * Report or log an exception.
     *
     * @param  \Throwable  $exception
     * @return void
     *
     * @throws \Exception
     */
    public function report(Throwable $exception)
    {
        parent::report($exception);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Throwable  $exception
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \Throwable
     */
    public function render($request, Throwable $exception)
    {
        return parent::render($request, $exception);
    }
}

Confused how to proceed - how would you build the exception class for Stripe?

0 likes
9 replies
bugsysha's avatar

Docs state:

Instead of type-checking exceptions in the exception handler's report and render methods, you may define report and render methods directly on your custom exception. When these methods exist, they will be called automatically by the framework.

connecteev's avatar

@bugsysha I tried that, here's the problem, I don't know how to access the $exception object in the render() function (so that I can then call $e->getError() etc, and the docs dont give an example how to do it.

Here's what I've tried:


<?php

namespace App\Exceptions;

use Throwable;
use Exception;
use App\Exceptions\Handler;

class MyStripeException extends Exception
{
    public function __construct($message = '', $code = 0, Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }


    public function report(Exception $e)
    {
        // $e = $this;
        \Log::emergency('In MyStripeException::report(): ' .
            // 'Status is:' . $e->getHttpStatus() .
            'Type is:' . $e->getError()->type .
            'Code is:' . $e->getError()->code .
            'Param is:' . $e->getError()->param .
            'Message is:' . $e->getError()->message
        );

This is what I see in the logs:

[2020-05-13 14:27:34] local.EMERGENCY: throw MyStripeException  
[2020-05-13 14:27:34] local.ERROR: Unresolvable dependency resolving [Parameter #0 [ <optional> $message ]] in class Exception {"userId":1,"exception":"[object] (Illuminate\Contracts\Container\BindingResolutionException(code: 0): Unresolvable dependency resolving [Parameter #0 [ <optional> $message ]] in class Exception at /app/vendor/laravel/framework/src/Illuminate/Container/Container.php:1026)
[stacktrace]
#0 /app/vendor/laravel/framework/src/Illuminate/Container/Container.php(939): Illuminate\Container\Container->unresolvablePrimitive(Object(ReflectionParameter))
#1 /app/vendor/laravel/framework/src/Illuminate/Container/Container.php(874): Illuminate\Container\Container->resolvePrimitive(Object(ReflectionParameter))
#2 /app/vendor/laravel/framework/src/Illuminate/Container/Container.php(836): Illuminate\Container\Container->resolveDependencies(Array)
#3 /app/vendor/laravel/framework/src/Illuminate/Container/Container.php(687): Illuminate\Container\Container->build('Exception')
connecteev's avatar

I also tried


    public function report()
    {
        $e = $this;
...

but had no luck either

bugsysha's avatar

Why do you need anything from $e when you are creating a brand new exception for a specific issue which you are throwing in the system?

bugsysha's avatar

Also just try

$this->getMessage();
$this->getCode();
$this->getBlah();

Here are methods on \Exception object:

     "__construct",
     "__wakeup",
     "getMessage",
     "getCode",
     "getFile",
     "getLine",
     "getTrace",
     "getPrevious",
     "getTraceAsString",
     "__toString",
connecteev's avatar

@bugsysha I need the exception data because I need to act on it differently, depending on what the exception thrown by Stripe is. I am trying to centralize all the Stripe exception handling, and Stripe can throw one of many exeptions, like \Stripe\Exception\RateLimitException, \Stripe\Exception\CardException, etc.

I did try using $this and it didn't work:

        try {
            $stripeCustomer = $user->asStripeCustomer();
            $defaultPaymentMethodId = $stripeCustomer->invoice_settings->default_payment_method;
            return $defaultPaymentMethodId;

        } catch (\Exception $e) {
            \Log::emergency('throw MyStripeException');
            throw new \App\Exceptions\MyStripeException(
                "{$e->getError()->message}", $e->getCode(), null
            );
        }

In class MyStripeException:

namespace App\Exceptions;

use Throwable;
use Exception;
use App\Exceptions\Handler;

class MyStripeException extends Exception
{
    // see vendor/laravel//cashier/src/Exceptions/IncompletePayment.php
    public function __construct($message = '', $code = 0, Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
    public function report()
    {
        // $e = Handler::prepareException($this);
        // $e = $this;
        \Log::emergency('In MyStripeException::report(): ' .
            $this->getMessage()
            // 'Status is:' . $e->getHttpStatus() .
            // 'Type is:' . $e->getError()->type .
            // 'Code is:' . $e->getError()->code .
            // 'Param is:' . $e->getError()->param .
            // 'Message is:' . $e->getError()->message
        );
   }

This is what I see in the logs:

[2020-05-13 15:12:58] local.EMERGENCY: throw MyStripeException  
[2020-05-13 15:12:58] local.EMERGENCY: In MyStripeException::report(): dude No such customer: dss  
[2020-05-13 15:12:58] local.EMERGENCY: Something else happened, completely unrelated to Stripe  
[2020-05-13 15:12:58] local.ERROR: Call to undefined method Exception::report() {"userId":1,"exception":"[object] (Error(code: 0): Call to undefined method Exception::report() at /app/Exceptions/MyStripeException.php:63)
[stacktrace]
connecteev's avatar
connecteev
OP
Best Answer
Level 11

Figured it out, basically the signatures weren't matching:

In the controller:

    private function getDefaultPaymentMethodId(User $user)
    {
        try {
            $stripeCustomer = $user->asStripeCustomer();
            $defaultPaymentMethodId = $stripeCustomer->invoice_settings->default_payment_method;
            return $defaultPaymentMethodId;

        } catch (\Exception $e) {
            \Log::emergency('throw MyStripeException');
            throw new \App\Exceptions\MyStripeException($e);
        }
    }

In app\Exceptions\MyStripeException.php:


<?php

namespace App\Exceptions;

use Throwable;
use Exception;
use App\Exceptions\Handler;

class MyStripeException extends Exception
{
    public $exception;

    public function __construct(Exception $e)
    {
        $this->exception = $e;
        parent::__construct($e->getError()->message, $e->getCode(), null);
    }

    /**
     * Report or log an exception.
     *
     * @param  \Throwable  $exception
     * @return void
     *
     * @throws \Exception
     */
    public function report()
    {
        $e = $this->exception;
        \Log::emergency('In MyStripeException::report(): ' .
            ' Exception Message is:' . $e->getMessage() .
            ' Status is:' . $e->getHttpStatus() .
            ' Type is:' . $e->getError()->type .
            ' Code is:' . $e->getError()->code .
            ' Param is:' . $e->getError()->param .
            ' Message is:' . $e->getError()->message
        );

        if ($e instanceof \Stripe\Exception\CardException) {
            // Since it's a decline, \Stripe\Exception\CardException will be caught
            \Log::emergency("Since it's a decline, \Stripe\Exception\CardException will be caught");

        } else if ($e instanceof \Stripe\Exception\RateLimitException) {
            // Too many requests made to the API too quickly
            \Log::emergency("Too many requests made to the API too quickly");
        } else if ($e instanceof \Stripe\Exception\InvalidRequestException) {
            // Invalid parameters were supplied to Stripe's API
            \Log::emergency("Invalid parameters were supplied to Stripe's API");
        } else if ($e instanceof \Stripe\Exception\AuthenticationException) {
            // Authentication with Stripe's API failed
            // (maybe you changed API keys recently)
            \Log::emergency("Authentication with Stripe's API failed (maybe you changed API keys recently)");
        } else if ($e instanceof \Stripe\Exception\ApiConnectionException) {
            // Network communication with Stripe failed
            \Log::emergency("Network communication with Stripe failed");
        } else if ($e instanceof \Stripe\Exception\ApiErrorException) {
            // Display a very generic error to the user, and maybe send
            // yourself an email
            \Log::emergency("Display a very generic error to the user, and maybe send yourself an email");

        } else {
            // Something else happened, completely unrelated to Stripe
            \Log::emergency("Something else happened, completely unrelated to Stripe");
            parent::report($e);
        }
    }
}

bugsysha's avatar

You did not understand what I was saying but never mind. Glad you've solved it.

Please or to participate in this conversation.