FOSRestBundle setup for return JSON but still asking for Twig template

13,387

Solution 1

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.

Solution 2

You can give response in two ways

return View::create($entities, Codes::HTTP_OK);

or

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

Solution 3

You can use simply

            $view = new View($form);
            $view->setFormat('json');
            return $this->handleView($view);

Solution 4

FosRestBundle leverages the Accept Header. This means that it returns a response based on what you request. By accessing the route "app_dev.php/api/v1/reps", you are implicitly requesting an html format, so it tries to provide a template.

Does app_dev.php/api/v1/reps.json return what you need?

You should also test app_dev.php/api/v1/reps.xml and expect an xml output

Share:
13,387
ReynierPM
Author by

ReynierPM

A passionate programmer and web developer with a background in front-end and back-end development, which is what he's been doing for over eight years. I had experience in web development using PHP (mostly), MySQL and JavaScript. I follows two major principles everyday work: beauty and simplicity. I believes everyone should learn something new every day. While I'm not working, I spends time coding personal projects, learning, watching screen casts, blogging, etc. Some specific areas of interest for me include cloud computing and anything related to web development among other like system and database administration.

Updated on June 23, 2022

Comments

  • ReynierPM
    ReynierPM almost 2 years

    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

  • ReynierPM
    ReynierPM almost 9 years
    None of those three works for me, when I put .html or .json or .xml I got 404 route error, I can't find what I'm missing in my config or at controller side
  • ReynierPM
    ReynierPM almost 9 years
    This way works but only from Postman Chrome extension by setting the right headers for Content-Type if I try from browser I got the same issue with templating which makes me ask: why? where is the issue on the configuration and why is not headers setup right? Also I like to return some kind of NotFoundException if there is not $entities, can you show me how to achieve that on your code?
  • le0diaz
    le0diaz over 8 years
    Your response has the solution but it's implicit in the content. You should specifically describe that for testing with some clients like Chrome/Firefox RestClient extension One HAVE TO set the proper header: Accept:application/json