关于使用Symfony/FosRestBundle/JMS Serializer实现字段白名单的建议


Advice for implementing field whitelists with Symfony/FosRestBundle/JMS Serializer

我目前正在学习如何使用Symfony 3(带FOSRestBundle)和JMS序列化程序实现一个相对简单的API。我最近一直在尝试实现作为消费客户端指定响应中应返回哪些字段(请求实体和关系中的字段)的功能。例如

  • 不包含查询字符串的/posts将返回所有Post实体属性(例如标题、正文、posted_at等),但没有关系
  • /posts?fields[]=id&fields[]=title将只返回帖子的id和标题(但是,没有关系
  • /posts?include[]=comment将包括上述内容,但具有Comment关系(及其所有属性)
  • /posts?include[]=comment&include[]=comment.author将如上所述返回,但也会在每个评论中包含作者

这是一件明智的事情吗?我最近对此做了很多研究,我看不出我可以1)限制单个字段的检索,2)只在明确要求的情况下返回相关实体。

我已经对这个概念进行了一些初步的尝试,然而,即使在确保我的存储库只返回Post实体(即没有注释)时,JMS序列化程序似乎也会触发所有相关实体的延迟加载,我似乎无法阻止这一点。我看到了一些链接,比如这个例子,但修复似乎不起作用(例如,在那个链接中,注释掉的$object->__load()调用在原始代码中永远无法到达

我已经使用JMSSerializer的Group功能实现了一个基于关系的示例,但这样做感觉很奇怪,因为理想情况下,我可以构建一个Doctrine Querybuilder实例,动态添加andWhere()调用,并让序列化程序只返回确切的数据,而不加载关系。

我很抱歉说得太多了,但我已经坚持了一段时间了,如果有任何意见,我将不胜感激!非常感谢。

您应该能够使用Groups排除策略实现您想要的目标。

例如,您的Post实体可能如下所示:

use JMS'Serializer'Annotation as JMS;
/**
 * @JMS'ExclusionPolicy("all")
 */
class Post
{
    /**
     * @ORM'Id
     * @ORM'GeneratedValue(strategy="IDENTITY")
     * @ORM'Column(type="integer")
     *
     * @JMS'Expose
     * @JMS'Groups({"all", "withFooAssociation", "withoutAssociations"})   
     */
    private $id;
    /**
     * @ORM'Column(type="string")
     *
     * @JMS'Expose
     * @JMS'Groups({"all", "withFooAssociation", "withoutAssociations"})
     */
    private $title;
    /**
     * @JMS'Expose
     * @JMS'Groups({"all", "withFooAssociation"})
     *
     * @ORM'OneToMany(targetEntity="Foo", mappedBy="post")
     */
    private $foos;
}

像这样,如果控制器操作使用serializerGroups={"all"}返回View,则响应将包含实体的所有字段。

如果使用serializerGroups={"withFooAssociation"},则响应将包含foos[]关联条目及其暴露的字段。

并且,如果它使用serializerGroups={"withoutAssociation"},则foos关联将被序列化程序排除,因此它将不会被渲染。

要从关联的目标实体(Foo实体)中排除属性,请对目标实体属性使用相同的Groups,以获得链式序列化策略。

当您的序列化结构良好时,您可以在控制器中动态设置serializerGroups,以便根据includefields参数(即/posts?fields[]=id&fields[]=title)使用不同的组。示例:

// PostController::getAction
use JMS'Serializer'SerializationContext;
use JMS'Serializer'SerializerBuilder;
$serializer = SerializerBuilder::create()->build();
$context = SerializationContext::create();
$groups = [];
// Assuming $request contains the "fields" param
$fields = $request->query->get('fields');
// Do this kind of check for all fields in $fields
if (in_array('foos', $fields)) {
    $groups[] = 'withFooAssociation';
}
// Tell the serializer to use the groups previously defined
$context->setGroups($groups);
// Serialize the data
$data = $serializer->serialize($posts, 'json', $context);
// Create the view
$view = View::create()->setData($data);
return $this->handleView($view);

我希望我能正确理解你的问题,这足以帮助你。