FOSRestBundle setup for return JSON but still asking for Twig template

ReynierPM picture ReynierPM · May 8, 2015 · Viewed 13k times · Source

I have configured FOSRestBundle as following:

#FOSRestBundle
fos_rest:
    param_fetcher_listener: true
    body_listener: true
    format_listener:
        rules:
            - { path: ^/, priorities: [ json, html ], fallback_format: ~, prefer_extension: true }
        media_type:
            version_regex: '/(v|version)=(?P<version>[0-9\.]+)/'

    body_converter:
        enabled: true
        validate: true

    view:
        mime_types:
            json: ['application/json', 'application/json;version=1.0', 'application/json;version=1.1']
        view_response_listener: 'force'
        formats:
            xml:  false
            json: true
        templating_formats:
            html: true

    exception:
        codes:
            'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
            'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT
        messages:
            'Symfony\Component\Routing\Exception\ResourceNotFoundException': true
    allowed_methods_listener: true
    access_denied_listener:
        json: true

And I have this at controller:

namespace PDI\PDOneBundle\Controller\Rest;

use FOS\RestBundle\Controller\FOSRestController;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\Get;

class RepresentativeRestController extends FOSRestController
{
    /**
     * Get all representatives.
     *
     * @return array
     *
     * @ApiDoc(
     *   resource = true,
     *       https = true,
     *   description = "Get all representatives.",
     *   statusCodes = {
     *      200 = "Returned when successful",
     *      400 = "Returned when errors"
     *   }
     * )
     * @Get("/api/v1/reps")
     */
    public function getRepsAction()
    {
        $em = $this->getDoctrine()->getManager();
        $entities = $em->getRepository('PDOneBundle:Representative')->findAll();

        if(!$entities)
        {
            return $this->view(null, 400);
        }

        return $this->view($entities, 200);
    }
}

But when I try the following URL app_dev.php/api/v1/reps I got this error:

Unable to find template "". 500 Internal Server Error - InvalidArgumentException 3 linked Exceptions: Twig_Error_Loader » InvalidArgumentException » InvalidArgumentException »

I expect that API return a well formed JSON as the following example:

{
   "id":"30000001",
   "veeva_rep_id":"0055648764067SwzAAE",
   "display_name":"John Know",
   "avatar_url":"http://freelanceme.net/Images/default%20profile%20picture.png",
   "rep_type":"VEEVA",
   "username":"[email protected]",
   "first":"John",
   "last":"Know",
   "title":"Sales Representative",
   "phone":"800-555-1212",
   "email":"[email protected]",
   "territory_id":"200454001",
   "inactive":"no",
   "total_contacts":"6",
   "total_shares":"0",
   "totalViews":"0",
   "lastLoginAt":"2015-05-05 15:45:57",
   "lastVeevaSyncAt":"2015-05-05 15:45:57",
   "createdAt":"2015-05-05 15:45:57",
   "updatedAt":"2015-05-05 15:45:57"
}

Is not FOSRestBundle configured for return JSON? Why still asking for Twig template? How can I fix this?

First test:

As @Jeet suggest me I have tried using Postman (is the same as the extension he told me) and after set the header Content-Type to application/json the error turns into this

Malformed JSON

so, the FOSRestBundle is not setting up headers as should be and controller is not returning a valid JSON, how do I fix those ones?

Second test:

As suggested by @Jeet I run this test:

/**
 * Get all representatives.
 *
 * @return array
 *
 * @ApiDoc(
 *   resource = true,
 *       https = true,
 *   description = "Get all representatives.",
 *   statusCodes = {
 *      200 = "Returned when successful",
 *      400 = "Returned when errors"
 *   }
 * )
 * @Get("/api/v1/reps")
 * @View()
 */
public function getRepsAction()
{
    $em = $this->getDoctrine()->getManager();
    $entities = $em->getRepository('PDOneBundle:Representative')->findAll();

    $temp = array("1", "2", "3");

    $view = $this->view($temp, Codes::HTTP_OK);
    return $this->handleView($view);
}

And still the same issue:

Unable to find template "". 500 Internal Server Error - InvalidArgumentException 3 linked Exceptions: Twig_Error_Loader » InvalidArgumentException » InvalidArgumentException »

What else can be wrong here? Did I'm missing something at configuration?

I forgot to add app/config/routing.yml and src/PDI/PDOneBundle/Resources/config/routing.yml at first so here them goes, perhaps this is the missing piece on the puzzle and give you a better idea of where the problem comes from:

#app/config/routing.yml
#PDOne
pdone:
    resource: "@PDOneBundle/Resources/config/routing.yml"

template:
    resource: "@TemplateBundle/Resources/config/routing.yml"

#FOSUserBundle
fos_user:
    resource: "@FOSUserBundle/Resources/config/routing/all.xml"
    prefix: /

#NelmioApiDocBundle:
NelmioApiDocBundle:
    resource: "@NelmioApiDocBundle/Resources/config/routing.yml"
    prefix:   /api/doc

#SonataAdmin
admin:
    resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
    prefix: /admin

_sonata_admin:
    resource: .
    type: sonata_admin
    prefix: /admin

#src/PDI/PDOneBundle/Resources/config/routing.yml
pdone:
    resource: "@PDOneBundle/Controller/"
    type:     annotation
    prefix:   /

Third test:

Definitely something is wrong with request from client side, if I use a tool like Postman and set proper headers I got the entities as I want, see pic below:

enter image description here

I can't find where the problem is so I desperately need someone's help here because I was already out of ideas

Answer

Artem L picture Artem L · May 12, 2015

As guys suggested: only Accept header or extension could give you a JSON. Seems like you've got this sorted with Accept header.

In order to use extension you must tell how do you want to set format things in Symfony.

This code should give you an output you want:

namespace RestTestBundle\Controller;

use FOS\RestBundle\Controller\Annotations\View;

use FOS\RestBundle\Controller\Annotations\Get;

class YourController
{
    /**
     * @Get("/api/v1/reps.{_format}", defaults={"_format"="json"})
     * @View()
     */
    public function indexAction()
    {
        return array(
            'status' => 'ok',
            'companies' => array(
                array('id' => 5),
                array('id' => 7),
            ),
        );
    }
}

Edit1: if you do not want to use a View class, but pure arrays: do not forget to disallow View handling of SensioExtraBundle

sensio_framework_extra:
    view:    { annotations: false }

Edit2: If you do not use HTML format and only want to have a json output you can use such fonfiguration:

fos_rest:
    # ....
    format_listener:
        rules:
            - { path: ^/, priorities: [ json ], fallback_format: json, prefer_extension: true }
    # ....

Explanation why you do see an error "view not found":

TL;DR: your browser send an Accept header that tells FOSRestBundle to output a 'html' variant.

Background: This bundle works mostly with Accept headers, it's a good practice to have all possible output formats that are available: html (you can test your REST API with forms you provide, lists of objects, details of objects easily this way), json, xml. Sometimes even image mime types like image/jpeg, image/png as default or json/xml as a variant (you can use base64 image representation).

Explanation: If you open up a "network" tab of a browser and check out headers it sends you will notice something like: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 which means "use in such order":

  1. text/html
  2. application/xhtml+xml
  3. application/xml with priority of 0.9 which is forbidden according to your configuration
  4. */* with priority 0.8 which meand any format

If you look close to this is you will see that according to your configuration text/html is one of variants that your configuration has ('html') and */* is another one ('json'), but text/html has a priority of 1, while */* has a priority of 0.8, so text/html matches and FOSRestBundle tries to find a HTML representation and fails.

PS: If you post question multiple times - please make sure you watch for all responses in every thread.