避免使用Doctrine实体和JMSserializer进行递归

时间:2022-12-14 06:47:44

I am building a REST API using Symfony2, Doctrine, FOSRestBundle and JMSSerializer.

我正在使用Symfony2,Doctrine,FOSRestBundle和JMSSerializer构建REST API。

The issue I am having is when serializing my entities, the serializer pulls in any related entities. Eg for a task that is part of a story which is part of a board, so when serializing the task I get output that includes the story which includes the board, which then includes all other stories on the board.

我遇到的问题是在序列化我的实体时,序列化程序会引入任何相关实体。例如,对于作为董事会一部分的故事的一部分的任务,因此在序列化任务时,我得到的输出包括包含董事会的故事,其中包括董事会上的所有其他故事。

Is there an easy way to limit this, and just include the foreignIds instead?

有没有一种简单的方法来限制这一点,而只是包含foreignIds?

5 个解决方案

#1


8  

Check the Serializer/Handler/DoctrineProxyHandler.php file on JMSSerializerBundle. Now, if you comment this line:

检查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();
            }

It will stop lazy loading your entities. If this is what you're looking for, then just go ahead and create your own handler where you don't lazy load.

它将停止延迟加载您的实体。如果这是您正在寻找的,那么只需继续创建您自己的处理程序,您不会延迟加载。

If this isn't correct, I recommend that you customize your entities before sending them to JMSSerializerBundle at your taste. For example, in any related entities I want the ID, while in others i need a custom column name like code, or name, or anything.

如果这不正确,我建议您根据自己的喜好自定义实体,然后再将它们发送到JMSSerializerBundle。例如,在我想要ID的任何相关实体中,而在其他实体中,我需要一个自定义列名,如代码,名称或任何东西。

I just create a copy of my entity object and then start getting the fields I need for relationships. Then, I serialize that copy. JMSSerializerBundle won't lazy load because I already provided the proper fields.

我只是创建我的实体对象的副本,然后开始获取我需要的关系字段。然后,我序列化该副本。 JMSSerializerBundle不会延迟加载,因为我已经提供了适当的字段。

#2


17  

Use JMS exclusion policy.

使用JMS排除策略。

Example using annotations on category entity, where you don't want to include children and product related entities to be included:

在类别实体上使用注释的示例,您不希望包含子项和要包含的产品相关实体:

use ...
    JMS\SerializerBundle\Annotation\ExclusionPolicy,
    JMS\SerializerBundle\Annotation\Exclude,
    ...;

/**
 * ...
 * @ExclusionPolicy("none")
 */
class Category
{
   /**
    * ...
    * @Exclude
    */
   private $children;

   /**
    * ...
    * @Exclude
    */
   private $products;

}

Look at the JMSSerializer docs for more information.

有关更多信息,请查看JMSSerializer文档。

EDIT:

编辑:

For example you could use partial keyword to select only data that you need. Although I could not, for the life of me, disable the loading of the full related entities (two levels down) if I pass entity object to the serializer (even when disabling load in DoctrineProxyHandler), but if I use an array, than it doesn't use doctrine lazy loading though proxies (as expected ofc).

例如,您可以使用partial关键字仅选择所需的数据。虽然我不能,在我的生活中,如果我将实体对象传递给序列化程序(即使在DoctrineProxyHandler中禁用加载)时禁用加载完整的相关实体(两个级别),但是如果我使用数组,那么不通过代理使用doctrine延迟加载(如预期的那样)。

Example using your example entities:

使用示例实体的示例:

$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();

This way you would get something like:

这样你会得到类似的东西:

[
{
    id: 33,
    title: "My Task",
    story: [
    {
        id: 554,
        board: [
        {
            id: 14,
            stories: [
            {
                id: 554
            },
            {
                id: 3424
            },
            {
                id: 3487
            }
            ]
        }
        ]
    }
    ]

}
]

P.S. I'm actually intrigued by this "problem". Anyway I'll see to come up with solution to how to serialize entity object without using array result.

附:我真的被这个“问题”所吸引。无论如何,我会看到如何在不使用数组结果的情况下序列化实体对象的解决方案。

#3


9  

Just an update in the latest version of JMSSerializer, the place you should look at is

只是最新版本的JMSSerializer的更新,你应该看看的地方

JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber

JMS \串行\此事件\用户\ DoctrineProxySubscriber

instead of

代替

Serializer\Handler\DoctrineProxyHandler

串行\处理器\ DoctrineProxyHandler

To override the default lazy load behaviour, one should define his own event subscriber.

要覆盖默认的延迟加载行为,应该定义自己的事件订阅者。

In your app/config.yuml add this:

在你的app / config.yuml中添加:

parameters:
    ...
    jms_serializer.doctrine_proxy_subscriber.class: Your\Bundle\Event\DoctrineProxySubscriber

you can copy the class from JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber to Your\Bundle\Event\DoctrineProxySubscriber and comment out the $object->__load(); line

你可以将类从JMS \ Serializer \ EventDispatcher \ Subscriber \ DoctrineProxySubscriber复制到你的\ 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));
    }
}

Update: I ended up writing my own simplified version of serialisation tool: https://github.com/dlin-me/array-converter-bundle

更新:我最终编写了自己的序列化工具简化版:https://github.com/dlin-me/array-converter-bundle

#4


0  

Here's a function to select the IDs of one-to-one or one-to-many associated entities in a generic way without using joins.

这是一种在不使用连接的情况下以通用方式选择一对一或一对多关联实体的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;
}

#5


0  

Here is the class which prevent lazy loading of one or many associations which can be used as JMS Serializer ExclusionStrategy.

这是防止延迟加载一个或多个关联的类,可以用作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://*.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;
    }
}

Usage example:

用法示例:

$serializationContext->addExclusionStrategy(
     new OnlyLoadedAssociationsExclusionStrategy()
);

#1


8  

Check the Serializer/Handler/DoctrineProxyHandler.php file on JMSSerializerBundle. Now, if you comment this line:

检查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();
            }

It will stop lazy loading your entities. If this is what you're looking for, then just go ahead and create your own handler where you don't lazy load.

它将停止延迟加载您的实体。如果这是您正在寻找的,那么只需继续创建您自己的处理程序,您不会延迟加载。

If this isn't correct, I recommend that you customize your entities before sending them to JMSSerializerBundle at your taste. For example, in any related entities I want the ID, while in others i need a custom column name like code, or name, or anything.

如果这不正确,我建议您根据自己的喜好自定义实体,然后再将它们发送到JMSSerializerBundle。例如,在我想要ID的任何相关实体中,而在其他实体中,我需要一个自定义列名,如代码,名称或任何东西。

I just create a copy of my entity object and then start getting the fields I need for relationships. Then, I serialize that copy. JMSSerializerBundle won't lazy load because I already provided the proper fields.

我只是创建我的实体对象的副本,然后开始获取我需要的关系字段。然后,我序列化该副本。 JMSSerializerBundle不会延迟加载,因为我已经提供了适当的字段。

#2


17  

Use JMS exclusion policy.

使用JMS排除策略。

Example using annotations on category entity, where you don't want to include children and product related entities to be included:

在类别实体上使用注释的示例,您不希望包含子项和要包含的产品相关实体:

use ...
    JMS\SerializerBundle\Annotation\ExclusionPolicy,
    JMS\SerializerBundle\Annotation\Exclude,
    ...;

/**
 * ...
 * @ExclusionPolicy("none")
 */
class Category
{
   /**
    * ...
    * @Exclude
    */
   private $children;

   /**
    * ...
    * @Exclude
    */
   private $products;

}

Look at the JMSSerializer docs for more information.

有关更多信息,请查看JMSSerializer文档。

EDIT:

编辑:

For example you could use partial keyword to select only data that you need. Although I could not, for the life of me, disable the loading of the full related entities (two levels down) if I pass entity object to the serializer (even when disabling load in DoctrineProxyHandler), but if I use an array, than it doesn't use doctrine lazy loading though proxies (as expected ofc).

例如,您可以使用partial关键字仅选择所需的数据。虽然我不能,在我的生活中,如果我将实体对象传递给序列化程序(即使在DoctrineProxyHandler中禁用加载)时禁用加载完整的相关实体(两个级别),但是如果我使用数组,那么不通过代理使用doctrine延迟加载(如预期的那样)。

Example using your example entities:

使用示例实体的示例:

$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();

This way you would get something like:

这样你会得到类似的东西:

[
{
    id: 33,
    title: "My Task",
    story: [
    {
        id: 554,
        board: [
        {
            id: 14,
            stories: [
            {
                id: 554
            },
            {
                id: 3424
            },
            {
                id: 3487
            }
            ]
        }
        ]
    }
    ]

}
]

P.S. I'm actually intrigued by this "problem". Anyway I'll see to come up with solution to how to serialize entity object without using array result.

附:我真的被这个“问题”所吸引。无论如何,我会看到如何在不使用数组结果的情况下序列化实体对象的解决方案。

#3


9  

Just an update in the latest version of JMSSerializer, the place you should look at is

只是最新版本的JMSSerializer的更新,你应该看看的地方

JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber

JMS \串行\此事件\用户\ DoctrineProxySubscriber

instead of

代替

Serializer\Handler\DoctrineProxyHandler

串行\处理器\ DoctrineProxyHandler

To override the default lazy load behaviour, one should define his own event subscriber.

要覆盖默认的延迟加载行为,应该定义自己的事件订阅者。

In your app/config.yuml add this:

在你的app / config.yuml中添加:

parameters:
    ...
    jms_serializer.doctrine_proxy_subscriber.class: Your\Bundle\Event\DoctrineProxySubscriber

you can copy the class from JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber to Your\Bundle\Event\DoctrineProxySubscriber and comment out the $object->__load(); line

你可以将类从JMS \ Serializer \ EventDispatcher \ Subscriber \ DoctrineProxySubscriber复制到你的\ 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));
    }
}

Update: I ended up writing my own simplified version of serialisation tool: https://github.com/dlin-me/array-converter-bundle

更新:我最终编写了自己的序列化工具简化版:https://github.com/dlin-me/array-converter-bundle

#4


0  

Here's a function to select the IDs of one-to-one or one-to-many associated entities in a generic way without using joins.

这是一种在不使用连接的情况下以通用方式选择一对一或一对多关联实体的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;
}

#5


0  

Here is the class which prevent lazy loading of one or many associations which can be used as JMS Serializer ExclusionStrategy.

这是防止延迟加载一个或多个关联的类,可以用作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://*.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;
    }
}

Usage example:

用法示例:

$serializationContext->addExclusionStrategy(
     new OnlyLoadedAssociationsExclusionStrategy()
);