FOSRestBundle setup for return JSON but still asking for Twig template
Asked Answered
P

4

13

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

Prosser answered 8/5, 2015 at 1:3 Comment(9)
Have you setup 'Content-Type' to be 'application/json' ?Tintinnabulum
@Tintinnabulum where I should do that? Is not at the configuration of FOSRes as you may see on the OP?Prosser
Try app_dev.php/api/v1/reps in Google Chrome's Advanced Rest Client application (app extention), there you will have an option.Tintinnabulum
@Tintinnabulum see the OP, I have edited adding some infoProsser
FOSRestBundle is responsible to set header for the response it's sending to client. But its client(Mobile APP, REST client extention)'s duty to set correct header values while sending it to server. The FOSRestBundle can respond accordingly. ;)Tintinnabulum
@Tintinnabulum how is that? I mean how I can fix that? What should the client sent to the server in order to get the proper response?Prosser
I guess you have done the changes from client side, Here $entities couldn't get normalized to a jason as I had same experience, Try sending a simple array and see if the JSON data coming fine.Tintinnabulum
@Tintinnabulum not working see my edit at OP I have added routes perhaps something is wrong there and I didn't see it beforeProsser
Are you testing the API path in simple web browser, cause that won't work, as I see it. The API consumer must set their header content-type to application/json as your Postman extention does.Tintinnabulum
G
26

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.

Gallice answered 12/5, 2015 at 14:35 Comment(0)
S
3

You can use simply

            $view = new View($form);
            $view->setFormat('json');
            return $this->handleView($view);
Sher answered 25/7, 2018 at 11:7 Comment(0)
R
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)
Raquel answered 8/5, 2015 at 7:32 Comment(1)
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?Prosser
I
1

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

Impossibly answered 8/5, 2015 at 7:8 Comment(2)
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 sideProsser
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/jsonLemur

© 2022 - 2024 — McMap. All rights reserved.