Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2 Annotation Inheritance

In my project, I have a BaseController class which implements several methods used by all of my controllers.

Now I wanted to add @QueryParam for that base class. My class looks like this:

class DoctrineRESTQueryController extends FOSRestController
{
    /**
     * @QueryParam(name="start",default=0)
     * @QueryParam(name="limit",default=null)
     */
    public function getQueryResponseAction (ParamFetcher $paramFetcher) {

    }
}

Now I have my actual controller which extends from my base controller:

class DefaultController extends DoctrineRESTQueryController {

    /**
     * Retrieves all SI Prefixes in the database
     *
     * @Routing\Route("/siprefix", defaults={"method" = "get","_format" = "json"})
     * @Routing\Method({"GET"})
     * @ApiDoc(output="array<PartKeepr\SiPrefixBundle\Entity\SiPrefix>")
     *
     * @View()
     *
     * {@inheritdoc}
     */
    public function getQueryResponseAction(ParamFetcher $paramFetcher) {
         $paramFetcher->get("start");
    }
}

Unfortunately, Symfony2 doesn't seem to inherit the @QueryParam annotations from the superclass, because the call to $paramFetcher->get("start") results in an exception "No @QueryParam/@RequestParam configuration for parameter 'start'".

Is there any way to make annotation inheritance work, or any other solution so I don't have to add @QueryParam to each of my controllers?

like image 578
Drachenkatze Avatar asked May 24 '15 16:05

Drachenkatze


1 Answers

There is no feature in FosRestBundle for this, so you must override parts of it for what you need, more specifically the method getParamsFromMethod in class FOSRestBundle/Request/ParamReader (see source code here).

This can be done through bundle inheritance.

First, subclass FOSRestBundle\Request\ParamReader in one of your bundles, e.g. YourSite\RestBundle\Request\MyParamReader and override getParamsFromMethod by loading the annotations of the parent method and merging them with those of the current one:

namespace YourSite\RestBundle\Request\MyParamReader;

use FOSRestBundle\Request\ParamReader;

class MyParamReader extends ParamReader
{
    public function getParamsFromMethod(\ReflectionMethod $method)
    {
        $parentParams = array();
        $params = parent::getParamsFromMethod($method);

        // This loads the annotations of the parent method
        $declaringClass = $method->getDeclaringClass();
        $parentClass = $declaringClass->getParentClass();

        if ($parentClass && $parentClass->hasMethod($method->getShortName())) {
            $parentMethod = $parentClass->getMethod($method->getShortName());
            $parentParams = parent::getParamsFromMethod($parentMethod);
        }

        return array_merge($params, $parentParams);
    }
}

You can modify the code to handle deep inheritance hierarchies, if necessary.

Now you should tell FOSRestBundle to use your YourSite\RestBundle\Request\MyParamReader class instead of FOSRestBundle\Request\ParamReader. You need to override the service definition, where the parameter reader is listed as a dependency. This is where bundle overriding/inheritance comes into play, see this Symfony2 article.

The service definition is located in the Resources/config/request.xml file (see source code here), FOSRestBundle\Request\ParamReader is a dependency of FOS\RestBundle\Request\ParamFetcher.

So you must override the Resources/config/request.xml file. To do this, following the above article, register your bundle and declare FOSRestBundle as its parent:

namespace YourSite\RestBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class YourSiteRestBundle extends Bundle
{
     public function getParent()
     {
          return 'FOSRestBundle';
     }
}

Create file YourSite\RestBundle\Resources\config\request.xml to add YourSite\RestBundle\Request\MyParamReader as a dependency:

<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <parameters>
        <parameter key="fos_rest.request.param_fetcher.class">FOS\RestBundle\Request\ParamFetcher</parameter>
        <parameter key="your_site_rest.request.param_fetcher.reader.class">YourSite\RestBundle\Request\MyParamReader</parameter>
    </parameters>
    <services>
        <service id="fos_rest.request.param_fetcher" class="%fos_rest.request.param_fetcher.class%" scope="request">
            <argument type="service" id="your_site.request.param_fetcher.reader"/>
            <argument type="service" id="request"/>
            <argument type="service" id="fos_rest.violation_formatter"/>
            <argument type="service" id="validator" on-invalid="null"/>
        </service>
        <service id="your_site.request.param_fetcher.reader" class="%your_site_rest.request.param_fetcher.reader.class%">
            <argument type="service" id="annotation_reader"/>
        </service>
    </services>
</container>

This is untested, but it should work.

like image 188
Genti Saliu Avatar answered Sep 25 '22 23:09

Genti Saliu