Search code examples
phpjsonsymfonyserializationfosrestbundle

Symfony - Filter Request with FOSRestBundle and body_listener without Annotations?


I am using Symfony2 with the FOSRestBundle. Is it possible to have the functionality of the @QueryParam and @RequestParam annotations without using annotations?

I am trying to build a json api (format), so I want to allow query params like include, page, filter, fields, and sort. My ideal way to handle this would be:

  1. Use the format_listener to detect it is json.
  2. Use a custom body_listener json handler to process the request so that it's similar to this.
  3. Have the controller validate the query/request params inside the action function, and throw an exception to be handled by the exception controller if it's invalid. (The body_listener would act as a helper to make extracting the data from the request easier in the controller, but the controller makes the decisions of what to do with that data.)

I'm mostly stuck on how to make a custom body_listener. I'm not sure if I would need to make a custom decoder or normalizer, and what that class might look like since they don't give any examples.

Rough code of what controller would look like:

<?php
namespace CoreBundle\Controller;

use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\View\View;
use FOS\RestBundle\Context\Context;
use Psr\Http\Message\ServerRequestInterface;

class SiteController extends FOSRestController
{   
    public function getAction($id, ServerRequestInterface $request)
    {
        try {
            // Validate $request. This is where the query/request
            // param annotation functionality would be replaced.
        } catch (Exception $e) {
            throw new InvalidRequestException($e);
        }

        $siteService = $this->get('app.site_service');
        $site = $siteService->getSite($id);

        $context = new Context();

        $context->setVersion($request->getVersion());
        // Ex: /sites/63?fields[sites]=name,address&fields[company]=foo,bar
        if ($request->hasIncludeFields()) {
            $context->addAttribute('include_fields', $request->getIncludeFields()); // Or however to do this
        }

        $view = new View($site, 200);

        $view->setContext($context);

        return $view;
    }
}

Solution

  • You can define parameters dynamically in param fetcher. It's described in documentation.

    For example:

    With annotations:

    <?php
    
    namespace ContentBundle\Controller\API;
    
    use FOS\RestBundle\Controller\FOSRestController;
    use FOS\RestBundle\Controller\Annotations\QueryParam;
    use FOS\RestBundle\Request\ParamFetcher;
    
    class PostController extends FOSRestController
    {
        /**
         * @QueryParam(name="sort", requirements="(asc|desc)", allowBlank=false, default="asc", description="Sort direction")
         */
        public function getPostsAction(ParamFetcher $paramFetcher)
        {
            $sort = $paramFetcher->get('sort');
        }
    }
    

    Without annotations:

    <?php
    
    namespace ContentBundle\Controller\API;
    
    use FOS\RestBundle\Controller\FOSRestController;
    use FOS\RestBundle\Controller\Annotations\QueryParam;
    use FOS\RestBundle\Request\ParamFetcher;
    
    class PostController extends FOSRestController
    {
       public function getPostsAction(ParamFetcher $paramFetcher)
        {
            $sort = new QueryParam();
            $sort->name = 'sort';
            $sort->requirements = '(asc|desc)';
            $sort->allowBlank = false;
            $sort->default = 'asc';
            $sort->description = 'Sort direction';
    
            $paramFetcher->addParam($sort);
    
            $param = $paramFetcher->get('sort');
            //
        }
    }