Search code examples
sessiondoctrineentitylazy-loadingsymfony5

Symfony 5: "Maybe you forget to persist it in the entity manager?" --> Alternative for Doctrine's depreciated merge()-function


When I reload an object from the session, I get an error "Maybe you forget to persist it in the entity manager?".

What can be done?


Solution

  • There are situations, when you want to store data in sessions. In general this works fine in Symfony. But once your data contains Doctrine-entities, your code will explode sooner or later.

    Doctrine is proposing the MERGE-function (see: https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/cookbook/entities-in-session.html), but this function is marked as depreciated.

    Here you will find a function to revitalize doctrine-data once loaded back from session to make your code work pretty and nice.

    Usage

    public function anyName() {
      // ...
      $data = $this->session->get('myData', $default_data);
      $data = $this->sessionHelper->revitalizeEntityObjects($data);
      // ...
    }
    

    Alternative to MERGE

    <?php
    
    
    namespace App\Service;
    
    
    use Doctrine\ORM\EntityManagerInterface;
    use ReflectionClass;
    
    class SessionHelper
    {
        const DB_NAMESPACE_NAME = "App\Entity";
    
        private $emi;
    
        public function __construct(EntityManagerInterface $emi) {
            $this->emi = $emi;
        }
    
        public function revitalizeEntityObjects(object $object)
        {
            $reflect = new ReflectionClass($object);
            $props   = $reflect->getProperties();
    
            foreach ($props as $prop) {
                $propName = $prop->getName();
                $propName = ucfirst($propName); // capitalize first letter
                $getter   = "get{$propName}";   // name of the getter-function
                $setter   = "set{$propName}";   // name of the setter-function
    
                // check if the property has a GETTER and SETTER (else go to next property)
                if ( ! $reflect->hasMethod($getter) ) { continue; }
                if ( ! $reflect->hasMethod($setter) ) { continue; }
    
                // get the value of the property
                $value = $object->$getter();
    
                // check if the value is an object (else go to next property)
                if ( ! is_object($value)) { continue; }
    
                // check if the object has the expected namespace-prefix (else go to next property)
                $subReflect = new ReflectionClass($value);
                if ($subReflect->getNamespaceName() !== self::DB_NAMESPACE_NAME) { continue; }
    
                // check if the ID-Field can be fetched by the standard 'getId'-function
                if ( ! $subReflect->hasMethod('getId') ) { continue; }
    
                // get the classname of the entity and the repository of that entity
                $classname  = $subReflect->getName();
                $repository = $this->emi->getRepository($classname);
    
                // re-fetch the object from the database
                $reloadedObject = $repository->find($value->getId());
    
                // check if something has been fetched (else do nothing and go to next property)
                if ( ! $reloadedObject) { continue; }
    
                // store the re-fetched object back into the main-object
                $object->$setter($reloadedObject);
    
            }
    
            return $object;
        }
    }
    

    Tim