使用 Symfony/FosRestBundle/JMS Serializer 实现字段白名单的建议
Advice for implementing field whitelists with Symfony/FosRestBundle/JMS Serializer
我目前正在学习如何使用 Symfony 3(带有 FOSRestBundle)和 JMS Serializer 来实现一个相对简单的 API。我最近一直在尝试实现指定功能,作为消费客户端,哪些字段应该在响应中 returned(所请求的实体和关系中的字段)。例如;
/posts
不包含查询字符串会 return 所有 Post
实体属性(例如标题、body、posted_at 等)但 没有关系.
/posts?fields[]=id&fields[]=title
return 只有帖子的 ID 和标题(但同样,没有关系)
/posts?include[]=comment
将包括上述但具有 Comment
关系(及其所有属性)
/posts?include[]=comment&include[]=comment.author
会 return 如上所述,但还会在每个评论中包含作者
尝试实施这种做法明智吗?我最近对此进行了大量研究,但我看不出我可以 1) 限制对单个字段的检索和 2) 只有 return 相关实体,如果它们被明确要求的话。
我对这个概念有过一些初步的尝试,但是即使确保我的存储库仅 return 是 Post 实体(即没有评论),JMS 序列化程序似乎会触发延迟加载所有相关实体,我似乎无法阻止这一点。我已经看到一些 links,例如 this example,但是修复似乎不起作用(例如在 link 中,注释掉的 $object->__load()
调用从未达到无论如何在原始代码中。
我已经实现了一个 relationship-based example of this using JMSSerializer's Group
functionality 但不得不这样做感觉很奇怪,当我理想情况下能够构建一个 Doctrine Querybuilder 实例,动态添加 andWhere()
调用并让序列化器只是return 准确的数据,无需加载关系。
很抱歉在这方面乱七八糟,但我已经坚持了一段时间,如果有任何意见,我将不胜感激!谢谢。
您应该能够通过 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;
}
像这样,如果您的控制器操作 returns a View
使用 serializerGroups={"all"}
,响应将包含您实体的所有字段。
如果它使用 serializerGroups={"withFooAssociation"}
,响应将包含 foos[]
关联条目及其 exposed 字段。
并且,如果它使用 serializerGroups={"withoutAssociation"}
,foos
关联将被序列化程序排除,因此不会被渲染。
要从关联的目标实体(Foo
实体)中排除属性,请在目标实体属性上使用相同的Groups
以获得链式序列化策略。
当序列化结构良好时,您可以在控制器中动态设置 serializerGroups
,以便根据 include
和 fields
参数(即 /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);
希望我正确理解了你的问题,这足以帮助你。
我目前正在学习如何使用 Symfony 3(带有 FOSRestBundle)和 JMS Serializer 来实现一个相对简单的 API。我最近一直在尝试实现指定功能,作为消费客户端,哪些字段应该在响应中 returned(所请求的实体和关系中的字段)。例如;
/posts
不包含查询字符串会 return 所有Post
实体属性(例如标题、body、posted_at 等)但 没有关系./posts?fields[]=id&fields[]=title
return 只有帖子的 ID 和标题(但同样,没有关系)/posts?include[]=comment
将包括上述但具有Comment
关系(及其所有属性)/posts?include[]=comment&include[]=comment.author
会 return 如上所述,但还会在每个评论中包含作者
尝试实施这种做法明智吗?我最近对此进行了大量研究,但我看不出我可以 1) 限制对单个字段的检索和 2) 只有 return 相关实体,如果它们被明确要求的话。
我对这个概念有过一些初步的尝试,但是即使确保我的存储库仅 return 是 Post 实体(即没有评论),JMS 序列化程序似乎会触发延迟加载所有相关实体,我似乎无法阻止这一点。我已经看到一些 links,例如 this example,但是修复似乎不起作用(例如在 link 中,注释掉的 $object->__load()
调用从未达到无论如何在原始代码中。
我已经实现了一个 relationship-based example of this using JMSSerializer's Group
functionality 但不得不这样做感觉很奇怪,当我理想情况下能够构建一个 Doctrine Querybuilder 实例,动态添加 andWhere()
调用并让序列化器只是return 准确的数据,无需加载关系。
很抱歉在这方面乱七八糟,但我已经坚持了一段时间,如果有任何意见,我将不胜感激!谢谢。
您应该能够通过 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;
}
像这样,如果您的控制器操作 returns a View
使用 serializerGroups={"all"}
,响应将包含您实体的所有字段。
如果它使用 serializerGroups={"withFooAssociation"}
,响应将包含 foos[]
关联条目及其 exposed 字段。
并且,如果它使用 serializerGroups={"withoutAssociation"}
,foos
关联将被序列化程序排除,因此不会被渲染。
要从关联的目标实体(Foo
实体)中排除属性,请在目标实体属性上使用相同的Groups
以获得链式序列化策略。
当序列化结构良好时,您可以在控制器中动态设置 serializerGroups
,以便根据 include
和 fields
参数(即 /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);
希望我正确理解了你的问题,这足以帮助你。