2
votes

My app has an entity called "client" that is injected into every request object.

To improve performance I've set up a custom cache, and am using JMSSerializer to serialize and cache client objects. When a request comes in, the cache returns the serialized Client object, and JMS deserializes it.

My application creates other entities, call them ChildEntities, and associates them with Client objects via a ManyToOne relationship on the ChildEntity.

This was working fine until I started loading these objects via deserializing the cached data, rather than using Doctrine to load them from the MySQL database. Doctrine now throws this error:

A new entity was found through the relationship "\acme\bundle\Entity\ChildEntity#client" that was not configured to cascade persist operations for entity: ClientName. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"})

So, if I'm understanding this error properly, it seems that Doctrine thinks my Client object which is referenced by a ManyToOne relationship on the ChildEntity object is a new object (even though I'm deserializing it with the ID) that it needs to save, but won't because no cascading is set up.

Is my strategy of deserializing objects wrong? Is there some way to deserialize them as Proxy objects, or some other solution to this problem? I prefer to use my custom cache to a doctrine cache.

2

2 Answers

3
votes

Figured this one out when I finally found the right search phrase.

I was using the default object constructor, I needed to be using the Doctrine Object Constructor.

To do so, I added this to my services.yml:

jms_serializer.object_constructor:
    alias: jms_serializer.doctrine_object_constructor
    public: false
1
votes

I use this module too and keep a serialized doctrine entity in session in json format.

So, I figured out that I had to configure Spea/JMSSerializerModule to use DoctrineObjectConstructor instead of UnserializeObjectConstructor.

But, contrary to UnserializeObjectConstructor, DoctrineObjectConstructor is not a simple invokable class. And the constructor needs two objects:

  • interface Doctrine\Common\Persistence\ManagerRegistry $managerRegistry
  • interface JMS\Serializer\Construction\ObjectConstructorInterface $fallbackConstructor

The problem is that there is no implementation of interface ManagerRegistry and obviously, no factory for it.

I resolved this by adding this dependency https://github.com/eddiejaoude/zf2-doctrine2-manager-registry-service in my composer.json. This module provides an implementation of interface ManagerRegistry through the service manager entry Doctrine\ManagerRegistry.

Once installed I created a factory for DoctrineObjectConstructor:

<?php
namespace Example\Service;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use JMS\Serializer\Construction\DoctrineObjectConstructor;

class DoctrineObjectConstructorFactory implements FactoryInterface
{
    /* (non-PHPdoc)
     * @see \Zend\ServiceManager\FactoryInterface::createService()
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serv1 = $serviceLocator->get('Doctrine\ManagerRegistry');
        $serv2 = $serviceLocator->get('jms_serializer.unserialize_object_constructor');
        $service = new DoctrineObjectConstructor($serv1, $serv2);
        return $service;
    }

}

And added these entries in my module.config.php:

'service_manager' => array(
    'factories' => array(
        'jms_serializer.doctrine_object_constructor' => 'Example\Service\DoctrineObjectConstructorFactory'
    ),
    'aliases' => array(
        'jms_serializer.object_constructor' => 'jms_serializer.doctrine_object_constructor'
    ),
),

That's it. Now, it works.

I wrote an issue for this here : https://github.com/Spea/JMSSerializerModule/issues/12 because I think we should not have to add external module to do this.