Customize controller for Laravel
Description
Following the documentation, with Symphony it is possible to override the controller or rely on provider/processor systems, while the Laravel integration is limited to provider/processors only. This can also be verified with a simple test case in Laravel.
This seems hard coded here: https://github.com/api-platform/core/blob/325a04c8303c25f04fd0cc107664965bc46b0c16/src/Laravel/routes/api.php#L49
Would it be possible to allow custom controllers here, for use cases where a bit more flexibility is generally helpful? There should also be a documented way to correctly serialize/deserialize contents for this case.
I am able to get the feature mostly working with the some slight changes:
- Update
api.phpto allow custom controllers - Update
ApiPlatformController.phpto allow custom actions for the body generation (to ensure better code reuse) - Create new
ApiPlatformCustomController.phpas an abstract class extendingApiPlatformController.php, to be extended by all custom controllers
I am currently finding an issue that the serializer by the default processor is for some reason not detecting the format correctly, leading to the following error: Serialization for the format \"\" is not supported - any help to solve this would be useful.
Details on my changes below.
On api.php:
- Add following line 48 to identify if a custom controller is set on the Operation attribute
$with_custom_controller =
$operation->getController() &&
$operation->getController() !=
"api_platform.action.not_exposed";
- Change the following code https://github.com/api-platform/core/blob/325a04c8303c25f04fd0cc107664965bc46b0c16/src/Laravel/routes/api.php#L49 to
"uses" => $with_custom_controller
? $operation->getController()
: ApiPlatformController::class,
To reuse as much code as possible from ApiPlatformController.php
https://github.com/api-platform/core/blob/325a04c8303c25f04fd0cc107664965bc46b0c16/src/Laravel/Controller/ApiPlatformController.php#L80
Edit to
$body = $this->customProvider($request, $operation, $uriVariables, $context);
and add method
protected function customProvider(Request $request, HttpOperation $operation, array $uriVariables = [], array $context = []) {
return $this->provider->provide($operation, $uriVariables, $context);
}
Then create ApiPlatformCustomController.php
// Controller/ApiPlatformController.php
declare(strict_types=1);
namespace ApiPlatform\Laravel\Controller;
use ApiPlatform\Metadata\HttpOperation;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
abstract class ApiPlatformCustomController extends ApiPlatformController
{
abstract public function handler(
Request $request,
HttpOperation $operation,
): mixed;
public function __invoke(Request $request): Response
{
$operation = $request->attributes->get("_api_operation");
if (!$operation) {
throw new \RuntimeException("Operation not found.");
}
if (!$operation instanceof HttpOperation) {
throw new \LogicException("Operation is not an HttpOperation.");
}
$operation->withRead(false);
$operation->withWrite(true);
return parent::__invoke($request);
}
protected function customProvider(
Request $request,
HttpOperation $operation,
array $uriVariables = [],
array $context = [],
) {
return $this->handler($request, $operation);
}
}
This will allow the following logic to work:
use ApiPlatform\Metadata\Get;
use Illuminate\Http\Request;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Laravel\Controller\ApiPlatformCustomController;
class CustomControllerTestResponse
{
public bool $test;
}
#[
Get(
uriTemplate: "/test/{id}",
controller: CustomControllerTest::class,
output: CustomControllerTestResponse::class,
),
]
final class CustomControllerTest extends ApiPlatformCustomController
{
public function handler(
Request $request,
HttpOperation $operation,
): CustomControllerTestResponse {
$response = new CustomControllerTestResponse();
$response->test = true;
return $response;
}
}
As noted, this will lead to error Serialization for the format \"\" is not supported, which is caused by the ApiPlatformMiddleware not finding or filling the _format attribute and/or the default processor not falling to a default for this case. More details to follow whenever I have time to check in depth, or if anyone is able to take a look.
This can be verified by manually changing the fallback _format from empty to jsonld which will get everything working correctly.
https://github.com/api-platform/core/blob/325a04c8303c25f04fd0cc107664965bc46b0c16/src/Laravel/ApiPlatformMiddleware.php#L45