schmittjoh / serializer

Library for (de-)serializing data of any complexity (supports JSON, and XML)
http://jmsyst.com/libs/serializer
MIT License
2.32k stars 589 forks source link

Prevent domino lazy-loading on deserialization. #1243

Open tlorens opened 4 years ago

tlorens commented 4 years ago
Q A
Bug report? yes
Feature request? no
BC Break report? maybe?
RFC? no

Steps required to reproduce the problem

  1. Upgraded Symfony 4.3.x -> 5.1.x
  2. Upgraded JMS SerializerBundle -> 3.7.0
  3. JMS SerializerBundle upgraded jms/metadata (2.3.0), jms/serializer(3.8.0)

Expected Result

Actual Result

I'm pretty certain this has something to do with the deserializer triggering doctrines lazy loader logic. I cannot use @exclude annotation logic. I'm not exactly sure how to prevent going down the rabbit hole of 3000+ queries. I had hand-crafted queries that limit the associations that get returned. These queries don't seem to fix that anymore. I have quite a few relationships serialized in the DB (think of a read-only snap-shot of all entities involved).

I'm not sure this has something to do with an upgrade to Doctrine or if it's a combination of upgrading both packages.

I'm sprinkled @exclude(if="") annotations around the entities, but that doesn't exactly solve the issue. I only did this to test to see what/where this was being triggered.

Note: I don't think this was an issue with symfony packages at v5.0.8 and appears to be something moving forward to v5.1

GameplayJDK commented 3 years ago

I think you're right. I can't tell what caused the changed behaviour since I haven't looked at the version history. But I think this is related to a problem I've recently experienced.

In my case I could not deserialize a doctrine entity when it is not tracked. I've traced my issue down to the following code and discovered that behavior can be changed using the bundle configuration. Look at the find(...) call to the ObjectManager, I think that's the part causing your queries to lazyload.

        // Entity update, load it from database
        $object = $objectManager->find($metadata->name, $identifierList);

        if (null === $object) {
            switch ($this->fallbackStrategy) {
                case self::ON_MISSING_NULL:
                    return null;
                case self::ON_MISSING_EXCEPTION:
                    throw new ObjectConstructionException(sprintf('Entity %s can not be found', $metadata->name));
                case self::ON_MISSING_FALLBACK:
                    return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
                default:
                    throw new InvalidArgumentException('The provided fallback strategy for the object constructor is not valid');
            }
        }

        $objectManager->initializeObject($object);

The serializer is updating every entity data on deserialization. An option to control this would be nice.

In your case I would suggest you cloning the entity before deserializing it. I've come to know that the cloned entity is not tracked by doctrine (as mentioned here) and thus can be deserialized without updating. Just make sure you set object_constructors.doctrine.fallback_strategy to fallback.

Just as a side note, setting that option solved my problem when I was about to open an issue for that.