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

chimit's avatar

How to catch a Policy response exception?

Official documentation tells that Policy responses throw AuthorizationException, but when I'm trying to catch and render this exception I get AccessDeniedHttpException instead.

// UserController.php
$this->authorize('get', $user);
// UserPolicy.php
public function get(User $user)
{
    // correctly throws AuthorizationException
    return Response::deny('My important message', 4062);
}
// Handler.php
/**
 * Register the exception handling callbacks for the application.
 *
 * @return void
 */
public function register()
{
    $this->renderable(function (AuthorizationException $e, $request) {
        // Doesn't work
        return $this->respondWithError($e->getCode() ?: 1010);
    });

    $this->renderable(function (AccessDeniedHttpException $e, $request) {
        // Is caught here
        return $this->respondWithError(1010);
    });
}

Why is the Laravel AuthorizationException transformed into the Symfony AccessDeniedHttpException? How can I get catch Policy response codes and messages in the Handler.php?

0 likes
5 replies
rodrigo.pedra's avatar

Before calling the render callbacks you registered, Laravel calls its internal ->prepareException(...) method:

https://github.com/laravel/framework/blob/35470afd598df6d8060ae106f2e4a82c6ace9272/src/Illuminate/Foundation/Exceptions/Handler.php#L314

And this method converts the AuthorizationException to a AccessDeniedHttpException:

https://github.com/laravel/framework/blob/35470afd598df6d8060ae106f2e4a82c6ace9272/src/Illuminate/Foundation/Exceptions/Handler.php#L362-L375

So your render callbacks for a AuthorizationException will never get called.

On the bright side you can get the previous exception from that one, check if it is an AuthorizationException and check the code for it:

$this->renderable(function (AccessDeniedHttpException $e, $request) {
    $previous = $e->getPrevious();

    if ($previous instanceof AuthorizationException) {
        // assuming ->respondWithError(...) is a private method
        // on your handler
        return $this->respondWithError($e->getCode() ?: 1010);
    }
    
    // let handler follow its course, AccessDeniedHttpException
    // is thrown from other places in the framework
    // unrelated to the Policies flow
    return null; 
});
4 likes
chimit's avatar

Thanks for the answer, Rodrigo!

I'm just curious why can't I work with native Laravel exceptions and not deal with Symfony's? I don't clearly understand the logic behind the decision for prepareException. I'm throwing a native Laravel exception with payload with Response::deny() and can't catch it without tricky checkings and involving Symfony classes. I feel it should be some recommended beautiful way to do it :)

1 like
rodrigo.pedra's avatar
Level 56

Well, they changed how the exception handler works a bit on Laravel 8.

I guess they convert to a symfony exception as it might have some helpers in place to set correct headers and response related stuff. The Symfony HTTP component is used many places by Laravel to handle HTTP requests and responses.

If you want to use the old-scholl (pre Laravel 8) approach, you can override the handler's render method on your app's exception handler instance, and catch the exception before calling the parent method:

<?php

namespace App\Exceptions;

// ...other imports
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    // ... other code

    public function render($request, Throwable $e)
    {
        if ($e instanceof AuthorizationException) {
            return $this->respondWithError($e->getCode() ?: 1010);
        }

        return parent::render($request, $e);
    }
}

If you do that you can skip registering the callbacks on the register method for those exceptions.

2 likes
chimit's avatar

Thanks! Apparently, it's the best way to have full control over exceptions rendering. Reversed back my Laravel 7 code :)

1 like

Please or to participate in this conversation.