FriendsOfSymfony / FOSRestBundle

This Bundle provides various tools to rapidly develop RESTful API's with Symfony
http://symfony.com/doc/master/bundles/FOSRestBundle/index.html
MIT License
2.79k stars 702 forks source link

AbstractFOSRestController break view handler registration #2029

Closed drekinov closed 4 years ago

drekinov commented 5 years ago

Hi, i hope that is not duplicate issue. i had API based on deprecated FOSRestController which is related to Symfony 4.2 "micro-container" controller and commands. So i decide to get rid of that deprecations and prepare for Symfony 5.x as soon as possible. i switched to AbstractFOSRestController and simply ported single service i need in controller (that is part of my BaseApiController, so fos_rest.view_handler is registered from AbstractFOSRestController):

/**
     * @return array
     */
    public static function getSubscribedServices()
    {
        return array_merge(
            parent::getSubscribedServices(),
            [
                AccountContextInterface::class => '?'.AccountContextInterface::class,
             ]
        );
    }

What break ?

When using AbstractFOSRestController my custom view handler which sign preformated S3 / Cloudfront urls at response level is not even registered in container. I am using xml syntax and that is working with FOSRestController:

<service id="App\Infrastructure\Symfony\Rest\JsonViewHandler" class="App\Infrastructure\Symfony\Rest\JsonViewHandler" autoconfigure="true" autowire="true">
        </service>
        <service id="app_fos_rest.view_handler" parent="fos_rest.view_handler.default">
            <call method="registerHandler">
                <argument>json</argument>
                <argument type="collection">
                    <argument type="service" id="App\Infrastructure\Symfony\Rest\JsonViewHandler"></argument>
                    <argument>createResponse</argument>
                </argument>
            </call>
        </service>
fos_rest:
    service:
        view_handler: app_fos_rest.view_handler

with AbstractFOSRestBundle .. registerHandler is not even called. however if i provide wrong service id then container complain so it is using app_fos_rest.view_handler but not calling "call" that is 1:1 example based on official RSSFeed view handler => https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/Resources/doc/examples/RssHandler.php

iak8 commented 4 years ago

having the same issue. Symfony 4.4. Could somebody explain how to fix that?

BThiebaut commented 4 years ago

Same issue here after upgrading to Symfony 4.4. Controller who extending AbstractFOSRestController is also register as a service by autowiring.

iak8 commented 4 years ago

after some debugging i found this.

When we extending FOSRestController getViewHandler() goes here

/**
 * Gets the public 'fos_rest.view_handler' shared service.
 *
 * @return \FOS\RestBundle\View\ViewHandler
 */
protected function getFosRest_ViewHandlerService()
{
    include_once \dirname(__DIR__, 4).'/vendor/friendsofsymfony/rest-bundle/View/ViewHandlerInterface.php';
    include_once \dirname(__DIR__, 4).'/vendor/friendsofsymfony/rest-bundle/View/ConfigurableViewHandlerInterface.php';
    include_once \dirname(__DIR__, 4).'/vendor/friendsofsymfony/rest-bundle/View/ViewHandler.php';
    include_once \dirname(__DIR__, 4).'/src/View/ViewHandler.php';

     $this->services['fos_rest.view_handler'] = $instance = new \FOS\RestBundle\View\ViewHandler(($this->services['router'] ?? $this->getRouterService()), ($this->privates['fos_rest.serializer.symfony'] ?? $this->getFosRest_Serializer_SymfonyService()), NULL, ($this->services['request_stack'] ?? ($this->services['request_stack'] = new \Symfony\Component\HttpFoundation\RequestStack())), ['json' => false, 'html' => true], 400, 204, false, ['html' => 302], 'twig', ['serializeNullStrategy' => true]);

    $instance->registerHandler('json', [0 => new \App\View\ViewHandler(), 1 => 'createResponse']);

    return $instance;
}

Stack trace:

srcApp_KernelDevDebugContainer.php:1011, ContainerZ2f2d2M\srcApp_KernelDevDebugContainer->getFosRest_ViewHandlerService()
Container.php:251, ContainerZ2f2d2M\srcApp_KernelDevDebugContainer->make()
Container.php:225, ContainerZ2f2d2M\srcApp_KernelDevDebugContainer->get()
FOSRestController.php:36, App\Controller\CustomerController->getViewHandler()
ControllerTrait.php:116, App\Controller\CustomerController->handleView()
CustomerController.php:32, App\Controller\CustomerController->cgetAction()
HttpKernel.php:146, Symfony\Component\HttpKernel\HttpKernel->handleRaw()
HttpKernel.php:68, Symfony\Component\HttpKernel\HttpKernel->handle()
Kernel.php:201, App\Kernel->handle()
index.php:25, {main}()

Here we have $instance->registerHandler() call and custom handler is registered.

But when we extending AbstractFOSRestController getViewHandler() goes here

/**
 * Gets the private 'fos_rest.view_handler.default' shared service.
 *
 * @return \FOS\RestBundle\View\ViewHandler
 */
protected function getFosRest_ViewHandler_DefaultService()
{
    include_once \dirname(__DIR__, 4).'/vendor/friendsofsymfony/rest-bundle/View/ViewHandlerInterface.php';
    include_once \dirname(__DIR__, 4).'/vendor/friendsofsymfony/rest-bundle/View/ConfigurableViewHandlerInterface.php';
    include_once \dirname(__DIR__, 4).'/vendor/friendsofsymfony/rest-bundle/View/ViewHandler.php';

    return $this->privates['fos_rest.view_handler.default'] = new \FOS\RestBundle\View\ViewHandler(($this->services['router'] ?? $this->getRouterService()), ($this->privates['fos_rest.serializer.symfony'] ?? $this->getFosRest_Serializer_SymfonyService()), NULL, ($this->services['request_stack'] ?? ($this->services['request_stack'] = new \Symfony\Component\HttpFoundation\RequestStack())), ['json' => false, 'html' => true], 400, 204, false, ['html' => 302], 'twig', ['serializeNullStrategy' => true]);
}

Stack trace:

srcApp_KernelDevDebugContainer.php:3557, Container0atNyx0\srcApp_KernelDevDebugContainer->getFosRest_ViewHandler_DefaultService()
Container.php:444, Container0atNyx0\srcApp_KernelDevDebugContainer->getService()
ServiceLocator.php:40, Symfony\Component\DependencyInjection\Argument\ServiceLocator->get()
AbstractFOSRestController.php:32, App\Controller\CustomerController->getViewHandler()
ControllerTrait.php:116, App\Controller\CustomerController->handleView()
CustomerController.php:32, App\Controller\CustomerController->cgetAction()
HttpKernel.php:146, Symfony\Component\HttpKernel\HttpKernel->handleRaw()
HttpKernel.php:68, Symfony\Component\HttpKernel\HttpKernel->handle()
Kernel.php:201, App\Kernel->handle()
index.php:25, {main}()

No $instance->registerHandler() call and no custom handler registration...

enricog84 commented 4 years ago

I think I found the issue: Inside Resources/config/view.xml the service FOS\RestBundle\View\ViewHandlerInterface is defined as:

<service id="FOS\RestBundle\View\ViewHandlerInterface" alias="fos_rest.view_handler.default" />

And the AbstractFOSRestController points to the ViewHandlerInterface::class as service, which is always fos_rest.view_handler.default (as defined above) and not the customized service:

    public static function getSubscribedServices()
    {
        $subscribedServices = parent::getSubscribedServices();
        $subscribedServices['fos_rest.view_handler'] = ViewHandlerInterface::class;

        return $subscribedServices;
    }

As a workaround I did decorate the fos_rest.view_handler.default so it would get overwritten with my definition, e.g.:

    wm.api.view_handler:
        parent: fos_rest.view_handler.default
        decorates: fos_rest.view_handler.default
        calls:
            - [registerHandler, [ 'haljson', ["@wm.api.response_view_handler_service", 'createHalJsonResponse'] ] ]

Shouldn't the alias for FOS\RestBundle\View\ViewHandlerInterface be fos_rest.view_handler instead of fos_rest.view_handler.default?