我正在使用 Symfony2、Doctrine、FOSRestBundle 和 JMSSerializer 构建一个 REST API。
我遇到的问题是在序列化我的实体时,序列化程序会引入任何相关实体。例如,对于作为故事一部分的任务,该故事是董事会的一部分,因此在序列化任务时,我得到的输出包括包含董事会的故事,然后包括董事会上的所有其他故事。
有没有一种简单的方法来限制这一点,而只包含foreignIds?
我正在使用 Symfony2、Doctrine、FOSRestBundle 和 JMSSerializer 构建一个 REST API。
我遇到的问题是在序列化我的实体时,序列化程序会引入任何相关实体。例如,对于作为故事一部分的任务,该故事是董事会的一部分,因此在序列化任务时,我得到的输出包括包含董事会的故事,然后包括董事会上的所有其他故事。
有没有一种简单的方法来限制这一点,而只包含foreignIds?
使用 JMS 排除策略。
在类别实体上使用注释的示例,您不想在其中包含要包含的子实体和产品相关实体:
use ...
JMS\SerializerBundle\Annotation\ExclusionPolicy,
JMS\SerializerBundle\Annotation\Exclude,
...;
/**
* ...
* @ExclusionPolicy("none")
*/
class Category
{
/**
* ...
* @Exclude
*/
private $children;
/**
* ...
* @Exclude
*/
private $products;
}
查看JMSSerializer 文档以获取更多信息。
编辑:
例如,您可以使用 partial 关键字来仅选择您需要的数据。尽管如果我将实体对象传递给序列化程序(即使在 DoctrineProxyHandler 中禁用加载),我终其一生都无法禁用完整相关实体的加载(向下两层),但如果我使用数组,则比它通过代理不使用原则延迟加载(正如预期的那样)。
使用您的示例实体的示例:
$dql = "SELECT t, s, partial b.{id}, partial ss.{id}
FROM Acme\AppBundle\Entity\Task t
JOIN t.story s
JOIN s.board b
JOIN b.stories ss"
$q = $this->_em-createQuery($dql);
$result = $q->getArrayResult();
这样你会得到类似的东西:
[
{
id: 33,
title: "My Task",
story: [
{
id: 554,
board: [
{
id: 14,
stories: [
{
id: 554
},
{
id: 3424
},
{
id: 3487
}
]
}
]
}
]
}
]
PS我实际上对这个“问题”很感兴趣。无论如何,我会看到如何在不使用数组结果的情况下序列化实体对象的解决方案。
只是最新版JMSSerializer的更新,该看的地方是
JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber
代替
序列化程序\处理程序\DoctrineProxyHandler
要覆盖默认的延迟加载行为,应该定义自己的事件订阅者。
在你的 app/config.yuml 添加这个:
parameters:
...
jms_serializer.doctrine_proxy_subscriber.class: Your\Bundle\Event\DoctrineProxySubscriber
您可以将类从 JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber 复制到 Your\Bundle\Event\DoctrineProxySubscriber 并注释掉 $object->__load(); 线
public function onPreSerialize(PreSerializeEvent $event)
{
$object = $event->getObject();
$type = $event->getType();
// If the set type name is not an actual class, but a faked type for which a custom handler exists, we do not
// modify it with this subscriber. Also, we forgo autoloading here as an instance of this type is already created,
// so it must be loaded if its a real class.
$virtualType = ! class_exists($type['name'], false);
if ($object instanceof PersistentCollection) {
if ( ! $virtualType) {
$event->setType('ArrayCollection');
}
return;
}
if ( ! $object instanceof Proxy && ! $object instanceof ORMProxy) {
return;
}
//$object->__load(); Just comment this out
if ( ! $virtualType) {
$event->setType(get_parent_class($object));
}
}
更新:我最终编写了自己的简化版本的序列化工具:https ://github.com/dlin-me/array-converter-bundle
检查 JMSSerializerBundle 上的Serializer/Handler/DoctrineProxyHandler.php文件。现在,如果您评论这一行:
public function serialize(VisitorInterface $visitor, $data, $type, &$handled)
{
if (($data instanceof Proxy || $data instanceof ORMProxy) && (!$data->__isInitialized__ || get_class($data) === $type)) {
$handled = true;
if (!$data->__isInitialized__) {
//$data->__load();
}
它将停止延迟加载您的实体。如果这是您正在寻找的内容,那么请继续创建您自己的处理程序,您不会在其中延迟加载。
如果这不正确,我建议您在根据自己的喜好将实体发送到 JMSSerializerBundle 之前对其进行自定义。例如,在任何相关实体中,我都需要 ID,而在其他实体中,我需要自定义列名,如代码、名称或任何内容。
我只是创建我的实体对象的副本,然后开始获取我需要的关系字段。然后,我序列化该副本。JMSSerializerBundle 不会延迟加载,因为我已经提供了正确的字段。
这是一个以通用方式选择一对一或一对多关联实体的 ID 而不使用连接的函数。
function selectWithAssociations($doctrine, $className) {
$em = $doctrine->getManager();
$meta = $em->getClassMetadata($className);
//explicitly get IDs of associated entities
$assocClauses = array();
foreach ($meta->getAssociationMappings() as $assocName => $assoc) {
if (isset($assoc['joinTable'])) {
//todo: doesn't handle many to many associations
} else {
$assocClauses[] = ", IDENTITY(e.$assocName) AS $assocName";
}
}
//run custom DQL query
$q = $em->createQuery('SELECT e AS _d' . implode('', $assocClauses) . ' FROM ' . $className . ' e');
$result = $q->getArrayResult();
return $result;
}
这是防止延迟加载一个或多个关联的类,可用作 JMS Serializer ExclusionStrategy。
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\Proxy;
use JMS\Serializer\Context;
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\SerializationContext;
/**
* Class OnlyLoadedAssociationsExclusionStrategy
*
* http://stackoverflow.com/questions/11851197/avoiding-recursion-with-doctrine-entities-and-jmsserializer
*/
class OnlyLoadedAssociationsExclusionStrategy implements ExclusionStrategyInterface
{
public function shouldSkipClass(ClassMetadata $metadata, Context $context)
{
}
public function shouldSkipProperty(PropertyMetadata $property, Context $context)
{
if ($context instanceof SerializationContext){
$vistingSet=$context->getVisitingSet();
//iterate over object to get last object
foreach ($vistingSet as $v){
$currentObject=$v;
}
$propertyValue=$property->getValue($currentObject);
if ($propertyValue instanceof Proxy){
// skip not loaded one association
if (!$propertyValue->__isInitialized__){
return true;
}
}
if ($propertyValue instanceof PersistentCollection){
// skip not loaded many association
if (!$propertyValue->isInitialized()){
return true;
}
}
}
return false;
}
}
使用示例:
$serializationContext->addExclusionStrategy(
new OnlyLoadedAssociationsExclusionStrategy()
);