FOSRestBundle配置返回JSON,但仍要求Twig模板。

13

我已按照以下方法配置了FOSRestBundle:

#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

我在控制器上有以下代码:

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);
    }
}

但是,当我尝试使用以下URL app_dev.php/api/v1/reps时,我得到了以下错误:

无法找到模板 "". 500内部服务器错误 - InvalidArgumentException 3个链接的异常:Twig_Error_Loader » InvalidArgumentException » InvalidArgumentException »

我期望该API返回一个格式良好的JSON,例如以下示例:

{
   "id":"30000001",
   "veeva_rep_id":"0055648764067SwzAAE",
   "display_name":"John Know",
   "avatar_url":"http://freelanceme.net/Images/default%20profile%20picture.png",
   "rep_type":"VEEVA",
   "username":"john@mail.com",
   "first":"John",
   "last":"Know",
   "title":"Sales Representative",
   "phone":"800-555-1212",
   "email":"john@mail.com",
   "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"
}

FOSRestBundle没有配置返回JSON吗?为什么还在要求Twig模板?我该如何解决这个问题?

第一个测试:

正如@Jeet建议的那样,我尝试使用Postman(就是他告诉我的扩展程序),并在将标头Content-Type设置为application/json后,错误变成了这样:

格式错误的JSON

因此,FOSRestBundle没有按照应该的方式设置标头,控制器未返回有效的JSON。我该如何解决这些问题呢?

第二个测试:

@Jeet所建议的,我运行了这个测试:

/**
 * 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);
}

问题仍然存在:

无法找到模板“”。500内部服务器错误 - InvalidArgumentException 3个链接的异常:Twig_Error_Loader » InvalidArgumentException » InvalidArgumentException »

这里还可能出了什么问题?我在配置中漏掉了什么吗?

一开始我忘记添加app/config/routing.ymlsrc/PDI/PDOneBundle/Resources/config/routing.yml,现在加上它们了,也许这是拼图中缺失的部分,可以更好地给你一个问题来自哪里的想法:

#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:   /

第三个测试:

客户端请求有问题,如果我使用像Postman这样的工具,并设置适当的头信息,我可以得到我想要的实体,如下图所示:

enter image description here

我找不到问题出在哪里,所以我非常需要有人帮助,因为我已经没有任何想法了。


你是否已将“Content-Type”设置为“application/json”? - Jeet
@Jeet 我应该在哪里做这个?不是在 FOSRes 的配置中,正如您在 OP 中所看到的那样吗? - ReynierPM
@Jeet 那怎么办呢?我的意思是我该如何修复它?客户端应该向服务器发送什么以获得正确的响应? - ReynierPM
我猜你是在客户端做了一些改动。这里的$entities无法正常转化为JSON,我之前也遇到过同样的问题。试着发送一个简单的数组,看看JSON数据是否正确返回。 - Jeet
@Jeet不起作用,请查看我在OP中的编辑,我已添加路由,也许有什么地方出了问题,之前我没有注意到。 - ReynierPM
显示剩余4条评论
4个回答

26

正如其他用户所建议的:只有Accept头或扩展名可以使您得到JSON。看起来您已经使用Accept头解决了这个问题。

为了使用扩展名,您必须告诉Symfony如何设置格式化内容。

这段代码应该给您想要的输出:

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),
            ),
        );
    }
}

编辑1:如果您不想使用View类,而是使用纯数组,请不要忘记禁止SensioExtraBundle处理视图。

sensio_framework_extra:
    view:    { annotations: false }

编辑2:如果您不使用HTML格式,只想要JSON输出,您可以使用以下配置:

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

解释为什么会出现“找不到视图”的错误:

TL;DR:您的浏览器发送了一个Accept标头,告诉FOSRestBundle输出“html”变体。

背景:该捆绑包大多使用Accept标头,最好将所有可用的输出格式包括在内:html(您可以通过提供的表单、对象列表、对象详情轻松测试REST API)、json、xml。有时甚至是默认的图像mime类型,如image/jpeg、image/png或json/xml作为变体(您可以使用base64图像表示)。

说明:如果您打开浏览器的“网络”选项卡并检查其发送的标头,您会注意到类似于以下内容:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,这意味着“按此顺序使用”:

  1. text/html
  2. application/xhtml+xml
  3. application/xml的优先级为0.9,根据您的配置是禁止的
  4. */*的优先级为0.8,表示任何格式

如果您仔细观察,就会发现根据您的配置文件,text/html是其中之一('html'),而*/*是另一个('json')。但是,text/html的优先级为1,而*/*的优先级为0.8,因此text/html匹配并且FOSRestBundle尝试查找HTML表示,但失败了。

PS:如果您多次发布问题,请确保在每个线程中观察所有响应。


3
您可以简单使用。
            $view = new View($form);
            $view->setFormat('json');
            return $this->handleView($view);

2
您可以通过两种方式进行响应。
return View::create($entities, Codes::HTTP_OK);

或者

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

这种方式可以工作,但只能通过在Postman Chrome扩展中设置正确的Content-Type标头来实现。如果我尝试从浏览器中进行,则会遇到与模板相关的相同问题,这使我想问:为什么?配置存在问题,为什么没有正确设置标头?此外,如果没有$entities,我希望返回某种NotFoundException,您能否向我展示如何在您的代码中实现它? - ReynierPM

1
FosRestBundle利用Accept标头,这意味着它会根据您的请求返回响应。通过访问路由“app_dev.php/api/v1/reps”,您隐含地请求一个HTML格式,因此它尝试提供一个模板。
app_dev.php/api/v1/reps.json是否返回所需内容?
您还应该测试app_dev.php/api/v1/reps.xml并期望获得一个XML输出。

1
没有一个能为我工作,当我加上 .html.json.xml 时,我收到了 404 路由错误,我找不到我的配置或控制器方面缺少什么。 - ReynierPM
你的回复中包含了解决方案,但它在内容中是隐含的。你应该明确地描述,对于像Chrome/Firefox RestClient扩展这样的一些客户端进行测试,必须设置正确的头部: Accept:application/json - le0diaz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接