ackerchez
ackerchez

Reputation: 1744

Laravel Custom Exceptions Configuration

I have been trying to implement some custom Exceptions in Laravel 5.3 with no luck.

I have created an AppException which extends the Exception class. The idea of this is that I would like to attach a user to my exceptions so I know who tripped it and can log it.

So I set up my custom exception like this:

class AppException extends Exception{

    protected $userId;

    public function __construct($message = '', $userId=0)
    {
        parent::__construct($message);
        $this->userId = $userId;
    }
}

Now what I would like is to use try catch's in my controller functions and then catch an exception and then rethrow the app exception so I can attach the user. Like this:

try{
    << code here >>
}
catch(Exception $e){
    throw new AppException($e->getMessage, $request->user()->id);
}

What I am finding is that I am unable to get a good trace stack because the line I am logging from my exception is the line from the rethrow in the catch, not from the actual exception.

What would be the right way to set this up? I am trying to do this in a way where I can utilize the built in Handler.php file that comes with Laravel instead of having to put in log code in every try catch.

Thanks!

Upvotes: 2

Views: 6098

Answers (2)

localheinz
localheinz

Reputation: 9582

Try attaching the actual exception to the thrown exception, for example like this:

class AppException extends \Exception
{
    /**
     * @var int
     */
    private $userId;

    /**
     * @param \Exception $exception
     * @param int        $userId
     *
     * @return self
     */
    public static function fromExceptionAndUserId(\Exception $exception, $userId)
    {
        $instance = new self(
            $exception->getMessage(),
            0,
            $exception
        );

        $instance->userId = $userId;

        return $instance;
    }

    /**
     * @return null|int
     */
    public function userId()
    {
        return $this->userId;
    }
}

Then in your controller:

try {
    // ...
} catch (\Exception $exception) {
    throw AppException::fromExceptionAndUserId(
        $exception, 
        $request->user()->id
    );
}

The idea is to not only use the actual exception message, but to also attach the actual exception as $previous exception.

Using a named constructor gives you the advantage that you can control how the exception message of AppException is constructed (while also attaching the user identifier), as well as still being able to use the original AppException constructor. It gives you the freedom to add more named constructors for different use cases, too.

For reference, see

Upvotes: 0

Federkun
Federkun

Reputation: 36924

The Exception class has a previous argument that you can use to retrieve all the previous Exception.

class AppException extends Exception
{
    private $userId;

    public function __construct($userId, $message = '', $code = 0, Exception $previous = null)
    {
        $this->userId = $userId;

        parent::__construct($message, $code, $previous);
    }

    public static function fromUserId($userId, Exception $previous = null)
    {
        return new static($userId, sprintf('Exception thrown from `%s` userid', $userId), 0, $previous);
    }

    public function getUserId()
    {
        return $this->userId;
    }
}

Or just simply

class AppException extends Exception
{
    public static function fromUserId($userId, Exception $previous = null)
    {
        return new static(sprintf('Exception thrown from `%s` userid', $userId), 0, $previous);
    }
}

After you catch AppException you can iterate all the exception like this:

do {
    printf("%s:%d %s (%d) [%s]\n", $e->getFile(), $e->getLine(), $e->getMessage(), $e->getCode(), get_class($e));
} while($e = $e->getPrevious());

Example:

try {
    try{
        throw new RuntimeException('Something bad happened.');
    }
    catch(Exception $e){
        throw AppException::fromUserId(123, $e);
    }
} catch(AppException $e) {
    do {
        printf("%s:%d %s (%d) [%s]\n", $e->getFile(), $e->getLine(), $e->getMessage(), $e->getCode(), get_class($e));
    } while($e = $e->getPrevious());
}

Upvotes: 8

Related Questions