API Platform version(s) affected: 4.1.25
Description
When using API Platform’s Laravel integration, custom error handler callbacks defined in bootstrap/app.php (e.g. via $exceptions->respond() or $exceptions->render()) are ignored because the deferred provider replaces the default error handler with its own decorated handler before the callbacks are even registered.
In the DeferredProvider, API Platform decorates the default error handler with its own (ApiPlatform\Laravel\Exception\ErrorHandler). Inside the render() method, it checks whether the request is an API Platform operation. If not, it delegates handling to the decorated service or the parent class:
$apiOperation = $this->initializeOperation($request);
if (!$apiOperation) {
return $this->decorated
? $this->decorated->render($request, $exception)
: parent::render($request, $exception);
}
Because the deferred provider registers its bindings before the custom render/respond callbacks are registered, it extends and decorates the default error handler that contains null callbacks. When its time to check if there are any custom render/respond callbacks, because we decorated a handler that never had them set, we don't have anything to run.
Impact
- Custom
$exceptions->render() or $exceptions->respond() definitions are never called.
- Developers cannot override exception handling when API Platform’s deferred provider is registered.
Proposed Solution
To restore expected behavior, the handler could directly call parent::render() instead of delegating to $this->decorated->render() when $apiOperation is false. This ensures Laravel’s exception handling pipeline (including user-defined callbacks) is respected. We may also try registering the APIP exception handler if the non-deferred provider. I have not tried this solution but it may be an option.
References
How to reproduce : Attempt to define your own render/respond callbacks in the bootstrap/app.php file and they fail to run.
If you dd() out the decorated handler, you'll noticed, even if you have defined your own callbacks, they have not been registered yet:
bootstrap/app.php:
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->respond(function (Response $response, Throwable $exception, Request $request) {
...
ApiPlatformDeferredProvider.php Line 258:
$this->app->extend(
ExceptionHandler::class,
function (ExceptionHandler $decorated, Application $app) {
dd($decorated);
/** @var ConfigRepository */
$config = $app['config'];
....
Output:
Illuminate\Foundation\Exceptions\Handler {#3084 ▼
#container: Illuminate\Foundation\Application {[#11 ▶](https://mi.test/asd#sf-dump-1026641604-ref211)}
#dontReport: []
#dontReportCallbacks: []
#reportCallbacks: []
#levels: []
#throttleCallbacks: []
#contextCallbacks: []
#renderCallbacks: []
#shouldRenderJsonWhenCallback: null
#finalizeResponseCallback: null
#exceptionMap: []
#hashThrottleKeys: true
#internalDontReport: array:12 [▶]
#dontFlash: array:3 [▶]
#withoutDuplicates: false
#reportedExceptionMap: WeakMap {#2898}
}
You'll notice finalizeResponseCallback is null, but this should be the registered respond() callback.
Additional Context
Happy to submit a PR - just need to know which direction the team would like to take.
API Platform version(s) affected: 4.1.25
Description
When using API Platform’s Laravel integration, custom error handler callbacks defined in
bootstrap/app.php(e.g. via$exceptions->respond()or$exceptions->render()) are ignored because the deferred provider replaces the default error handler with its own decorated handler before the callbacks are even registered.In the
DeferredProvider, API Platform decorates the default error handler with its own (ApiPlatform\Laravel\Exception\ErrorHandler). Inside therender()method, it checks whether the request is an API Platform operation. If not, it delegates handling to the decorated service or the parent class:Because the deferred provider registers its bindings before the custom render/respond callbacks are registered, it extends and decorates the default error handler that contains null callbacks. When its time to check if there are any custom render/respond callbacks, because we decorated a handler that never had them set, we don't have anything to run.
Impact
$exceptions->render()or$exceptions->respond()definitions are never called.Proposed Solution
To restore expected behavior, the handler could directly call
parent::render()instead of delegating to$this->decorated->render()when$apiOperationis false. This ensures Laravel’s exception handling pipeline (including user-defined callbacks) is respected. We may also try registering the APIP exception handler if the non-deferred provider. I have not tried this solution but it may be an option.References
ApiPlatform\Laravel\Exception\ErrorHandlerHow to reproduce : Attempt to define your own render/respond callbacks in the
bootstrap/app.phpfile and they fail to run.If you
dd()out the decorated handler, you'll noticed, even if you have defined your own callbacks, they have not been registered yet:bootstrap/app.php:ApiPlatformDeferredProvider.phpLine 258:Output:
You'll notice
finalizeResponseCallbackis null, but this should be the registeredrespond()callback.Additional Context
Happy to submit a PR - just need to know which direction the team would like to take.