vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 401

Open in your IDE?
  1. <?php
  2. /*
  3.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14.  *
  15.  * This software consists of voluntary contributions made by many individuals
  16.  * and is licensed under the MIT license. For more information, see
  17.  * <http://www.doctrine-project.org>.
  18.  */
  19. namespace Doctrine\ORM;
  20. use Doctrine\Common\Collections\ArrayCollection;
  21. use Doctrine\Common\Collections\Collection;
  22. use Doctrine\DBAL\LockMode;
  23. use Doctrine\ORM\Cache\Persister\CachedPersister;
  24. use Doctrine\ORM\Event\LifecycleEventArgs;
  25. use Doctrine\ORM\Event\ListenersInvoker;
  26. use Doctrine\ORM\Event\OnFlushEventArgs;
  27. use Doctrine\ORM\Event\PostFlushEventArgs;
  28. use Doctrine\ORM\Event\PreFlushEventArgs;
  29. use Doctrine\ORM\Event\PreUpdateEventArgs;
  30. use Doctrine\ORM\Internal\HydrationCompleteHandler;
  31. use Doctrine\ORM\Mapping\ClassMetadata;
  32. use Doctrine\ORM\Mapping\Reflection\ReflectionPropertiesGetter;
  33. use Doctrine\ORM\Persisters\Collection\ManyToManyPersister;
  34. use Doctrine\ORM\Persisters\Collection\OneToManyPersister;
  35. use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
  36. use Doctrine\ORM\Persisters\Entity\JoinedSubclassPersister;
  37. use Doctrine\ORM\Persisters\Entity\SingleTablePersister;
  38. use Doctrine\ORM\Proxy\Proxy;
  39. use Doctrine\ORM\Utility\IdentifierFlattener;
  40. use Doctrine\Persistence\Mapping\RuntimeReflectionService;
  41. use Doctrine\Persistence\NotifyPropertyChanged;
  42. use Doctrine\Persistence\ObjectManagerAware;
  43. use Doctrine\Persistence\PropertyChangedListener;
  44. use InvalidArgumentException;
  45. use Throwable;
  46. use UnexpectedValueException;
  47. use function get_class;
  48. use function spl_object_hash;
  49. /**
  50.  * The UnitOfWork is responsible for tracking changes to objects during an
  51.  * "object-level" transaction and for writing out changes to the database
  52.  * in the correct order.
  53.  *
  54.  * Internal note: This class contains highly performance-sensitive code.
  55.  *
  56.  * @since       2.0
  57.  * @author      Benjamin Eberlei <kontakt@beberlei.de>
  58.  * @author      Guilherme Blanco <guilhermeblanco@hotmail.com>
  59.  * @author      Jonathan Wage <jonwage@gmail.com>
  60.  * @author      Roman Borschel <roman@code-factory.org>
  61.  * @author      Rob Caiger <rob@clocal.co.uk>
  62.  */
  63. class UnitOfWork implements PropertyChangedListener
  64. {
  65.     /**
  66.      * An entity is in MANAGED state when its persistence is managed by an EntityManager.
  67.      */
  68.     const STATE_MANAGED 1;
  69.     /**
  70.      * An entity is new if it has just been instantiated (i.e. using the "new" operator)
  71.      * and is not (yet) managed by an EntityManager.
  72.      */
  73.     const STATE_NEW 2;
  74.     /**
  75.      * A detached entity is an instance with persistent state and identity that is not
  76.      * (or no longer) associated with an EntityManager (and a UnitOfWork).
  77.      */
  78.     const STATE_DETACHED 3;
  79.     /**
  80.      * A removed entity instance is an instance with a persistent identity,
  81.      * associated with an EntityManager, whose persistent state will be deleted
  82.      * on commit.
  83.      */
  84.     const STATE_REMOVED 4;
  85.     /**
  86.      * Hint used to collect all primary keys of associated entities during hydration
  87.      * and execute it in a dedicated query afterwards
  88.      * @see https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql
  89.      */
  90.     const HINT_DEFEREAGERLOAD 'deferEagerLoad';
  91.     /**
  92.      * The identity map that holds references to all managed entities that have
  93.      * an identity. The entities are grouped by their class name.
  94.      * Since all classes in a hierarchy must share the same identifier set,
  95.      * we always take the root class name of the hierarchy.
  96.      *
  97.      * @var array
  98.      */
  99.     private $identityMap = [];
  100.     /**
  101.      * Map of all identifiers of managed entities.
  102.      * Keys are object ids (spl_object_hash).
  103.      *
  104.      * @var array
  105.      */
  106.     private $entityIdentifiers = [];
  107.     /**
  108.      * Map of the original entity data of managed entities.
  109.      * Keys are object ids (spl_object_hash). This is used for calculating changesets
  110.      * at commit time.
  111.      *
  112.      * Internal note: Note that PHPs "copy-on-write" behavior helps a lot with memory usage.
  113.      *                A value will only really be copied if the value in the entity is modified
  114.      *                by the user.
  115.      *
  116.      * @var array
  117.      */
  118.     private $originalEntityData = [];
  119.     /**
  120.      * Map of entity changes. Keys are object ids (spl_object_hash).
  121.      * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.
  122.      *
  123.      * @var array
  124.      */
  125.     private $entityChangeSets = [];
  126.     /**
  127.      * The (cached) states of any known entities.
  128.      * Keys are object ids (spl_object_hash).
  129.      *
  130.      * @var array
  131.      */
  132.     private $entityStates = [];
  133.     /**
  134.      * Map of entities that are scheduled for dirty checking at commit time.
  135.      * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT.
  136.      * Keys are object ids (spl_object_hash).
  137.      *
  138.      * @var array
  139.      */
  140.     private $scheduledForSynchronization = [];
  141.     /**
  142.      * A list of all pending entity insertions.
  143.      *
  144.      * @var array
  145.      */
  146.     private $entityInsertions = [];
  147.     /**
  148.      * A list of all pending entity updates.
  149.      *
  150.      * @var array
  151.      */
  152.     private $entityUpdates = [];
  153.     /**
  154.      * Any pending extra updates that have been scheduled by persisters.
  155.      *
  156.      * @var array
  157.      */
  158.     private $extraUpdates = [];
  159.     /**
  160.      * A list of all pending entity deletions.
  161.      *
  162.      * @var array
  163.      */
  164.     private $entityDeletions = [];
  165.     /**
  166.      * New entities that were discovered through relationships that were not
  167.      * marked as cascade-persist. During flush, this array is populated and
  168.      * then pruned of any entities that were discovered through a valid
  169.      * cascade-persist path. (Leftovers cause an error.)
  170.      *
  171.      * Keys are OIDs, payload is a two-item array describing the association
  172.      * and the entity.
  173.      *
  174.      * @var object[][]|array[][] indexed by respective object spl_object_hash()
  175.      */
  176.     private $nonCascadedNewDetectedEntities = [];
  177.     /**
  178.      * All pending collection deletions.
  179.      *
  180.      * @var array
  181.      */
  182.     private $collectionDeletions = [];
  183.     /**
  184.      * All pending collection updates.
  185.      *
  186.      * @var array
  187.      */
  188.     private $collectionUpdates = [];
  189.     /**
  190.      * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
  191.      * At the end of the UnitOfWork all these collections will make new snapshots
  192.      * of their data.
  193.      *
  194.      * @var array
  195.      */
  196.     private $visitedCollections = [];
  197.     /**
  198.      * The EntityManager that "owns" this UnitOfWork instance.
  199.      *
  200.      * @var EntityManagerInterface
  201.      */
  202.     private $em;
  203.     /**
  204.      * The entity persister instances used to persist entity instances.
  205.      *
  206.      * @var array
  207.      */
  208.     private $persisters = [];
  209.     /**
  210.      * The collection persister instances used to persist collections.
  211.      *
  212.      * @var array
  213.      */
  214.     private $collectionPersisters = [];
  215.     /**
  216.      * The EventManager used for dispatching events.
  217.      *
  218.      * @var \Doctrine\Common\EventManager
  219.      */
  220.     private $evm;
  221.     /**
  222.      * The ListenersInvoker used for dispatching events.
  223.      *
  224.      * @var \Doctrine\ORM\Event\ListenersInvoker
  225.      */
  226.     private $listenersInvoker;
  227.     /**
  228.      * The IdentifierFlattener used for manipulating identifiers
  229.      *
  230.      * @var \Doctrine\ORM\Utility\IdentifierFlattener
  231.      */
  232.     private $identifierFlattener;
  233.     /**
  234.      * Orphaned entities that are scheduled for removal.
  235.      *
  236.      * @var array
  237.      */
  238.     private $orphanRemovals = [];
  239.     /**
  240.      * Read-Only objects are never evaluated
  241.      *
  242.      * @var array
  243.      */
  244.     private $readOnlyObjects = [];
  245.     /**
  246.      * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.
  247.      *
  248.      * @var array
  249.      */
  250.     private $eagerLoadingEntities = [];
  251.     /**
  252.      * @var boolean
  253.      */
  254.     protected $hasCache false;
  255.     /**
  256.      * Helper for handling completion of hydration
  257.      *
  258.      * @var HydrationCompleteHandler
  259.      */
  260.     private $hydrationCompleteHandler;
  261.     /**
  262.      * @var ReflectionPropertiesGetter
  263.      */
  264.     private $reflectionPropertiesGetter;
  265.     /**
  266.      * Initializes a new UnitOfWork instance, bound to the given EntityManager.
  267.      *
  268.      * @param EntityManagerInterface $em
  269.      */
  270.     public function __construct(EntityManagerInterface $em)
  271.     {
  272.         $this->em                         $em;
  273.         $this->evm                        $em->getEventManager();
  274.         $this->listenersInvoker           = new ListenersInvoker($em);
  275.         $this->hasCache                   $em->getConfiguration()->isSecondLevelCacheEnabled();
  276.         $this->identifierFlattener        = new IdentifierFlattener($this$em->getMetadataFactory());
  277.         $this->hydrationCompleteHandler   = new HydrationCompleteHandler($this->listenersInvoker$em);
  278.         $this->reflectionPropertiesGetter = new ReflectionPropertiesGetter(new RuntimeReflectionService());
  279.     }
  280.     /**
  281.      * Commits the UnitOfWork, executing all operations that have been postponed
  282.      * up to this point. The state of all managed entities will be synchronized with
  283.      * the database.
  284.      *
  285.      * The operations are executed in the following order:
  286.      *
  287.      * 1) All entity insertions
  288.      * 2) All entity updates
  289.      * 3) All collection deletions
  290.      * 4) All collection updates
  291.      * 5) All entity deletions
  292.      *
  293.      * @param null|object|array $entity
  294.      *
  295.      * @return void
  296.      *
  297.      * @throws \Exception
  298.      */
  299.     public function commit($entity null)
  300.     {
  301.         // Raise preFlush
  302.         if ($this->evm->hasListeners(Events::preFlush)) {
  303.             $this->evm->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
  304.         }
  305.         // Compute changes done since last commit.
  306.         if (null === $entity) {
  307.             $this->computeChangeSets();
  308.         } elseif (is_object($entity)) {
  309.             $this->computeSingleEntityChangeSet($entity);
  310.         } elseif (is_array($entity)) {
  311.             foreach ($entity as $object) {
  312.                 $this->computeSingleEntityChangeSet($object);
  313.             }
  314.         }
  315.         if ( ! ($this->entityInsertions ||
  316.                 $this->entityDeletions ||
  317.                 $this->entityUpdates ||
  318.                 $this->collectionUpdates ||
  319.                 $this->collectionDeletions ||
  320.                 $this->orphanRemovals)) {
  321.             $this->dispatchOnFlushEvent();
  322.             $this->dispatchPostFlushEvent();
  323.             $this->postCommitCleanup($entity);
  324.             return; // Nothing to do.
  325.         }
  326.         $this->assertThatThereAreNoUnintentionallyNonPersistedAssociations();
  327.         if ($this->orphanRemovals) {
  328.             foreach ($this->orphanRemovals as $orphan) {
  329.                 $this->remove($orphan);
  330.             }
  331.         }
  332.         $this->dispatchOnFlushEvent();
  333.         // Now we need a commit order to maintain referential integrity
  334.         $commitOrder $this->getCommitOrder();
  335.         $conn $this->em->getConnection();
  336.         $conn->beginTransaction();
  337.         try {
  338.             // Collection deletions (deletions of complete collections)
  339.             foreach ($this->collectionDeletions as $collectionToDelete) {
  340.                 if (! $collectionToDelete instanceof PersistentCollection) {
  341.                     $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
  342.                     continue;
  343.                 }
  344.                 // Deferred explicit tracked collections can be removed only when owning relation was persisted
  345.                 $owner $collectionToDelete->getOwner();
  346.                 if ($this->em->getClassMetadata(get_class($owner))->isChangeTrackingDeferredImplicit() || $this->isScheduledForDirtyCheck($owner)) {
  347.                     $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
  348.                 }
  349.             }
  350.             if ($this->entityInsertions) {
  351.                 foreach ($commitOrder as $class) {
  352.                     $this->executeInserts($class);
  353.                 }
  354.             }
  355.             if ($this->entityUpdates) {
  356.                 foreach ($commitOrder as $class) {
  357.                     $this->executeUpdates($class);
  358.                 }
  359.             }
  360.             // Extra updates that were requested by persisters.
  361.             if ($this->extraUpdates) {
  362.                 $this->executeExtraUpdates();
  363.             }
  364.             // Collection updates (deleteRows, updateRows, insertRows)
  365.             foreach ($this->collectionUpdates as $collectionToUpdate) {
  366.                 $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);
  367.             }
  368.             // Entity deletions come last and need to be in reverse commit order
  369.             if ($this->entityDeletions) {
  370.                 for ($count count($commitOrder), $i $count 1$i >= && $this->entityDeletions; --$i) {
  371.                     $this->executeDeletions($commitOrder[$i]);
  372.                 }
  373.             }
  374.             $conn->commit();
  375.         } catch (Throwable $e) {
  376.             $this->em->close();
  377.             $conn->rollBack();
  378.             $this->afterTransactionRolledBack();
  379.             throw $e;
  380.         }
  381.         $this->afterTransactionComplete();
  382.         // Take new snapshots from visited collections
  383.         foreach ($this->visitedCollections as $coll) {
  384.             $coll->takeSnapshot();
  385.         }
  386.         $this->dispatchPostFlushEvent();
  387.         $this->postCommitCleanup($entity);
  388.     }
  389.     /**
  390.      * @param null|object|object[] $entity
  391.      */
  392.     private function postCommitCleanup($entity) : void
  393.     {
  394.         $this->entityInsertions =
  395.         $this->entityUpdates =
  396.         $this->entityDeletions =
  397.         $this->extraUpdates =
  398.         $this->collectionUpdates =
  399.         $this->nonCascadedNewDetectedEntities =
  400.         $this->collectionDeletions =
  401.         $this->visitedCollections =
  402.         $this->orphanRemovals = [];
  403.         if (null === $entity) {
  404.             $this->entityChangeSets $this->scheduledForSynchronization = [];
  405.             return;
  406.         }
  407.         $entities = \is_object($entity)
  408.             ? [$entity]
  409.             : $entity;
  410.         foreach ($entities as $object) {
  411.             $oid spl_object_hash($object);
  412.             $this->clearEntityChangeSet($oid);
  413.             unset($this->scheduledForSynchronization[$this->em->getClassMetadata(\get_class($object))->rootEntityName][$oid]);
  414.         }
  415.     }
  416.     /**
  417.      * Computes the changesets of all entities scheduled for insertion.
  418.      *
  419.      * @return void
  420.      */
  421.     private function computeScheduleInsertsChangeSets()
  422.     {
  423.         foreach ($this->entityInsertions as $entity) {
  424.             $class $this->em->getClassMetadata(get_class($entity));
  425.             $this->computeChangeSet($class$entity);
  426.         }
  427.     }
  428.     /**
  429.      * Only flushes the given entity according to a ruleset that keeps the UoW consistent.
  430.      *
  431.      * 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well!
  432.      * 2. Read Only entities are skipped.
  433.      * 3. Proxies are skipped.
  434.      * 4. Only if entity is properly managed.
  435.      *
  436.      * @param object $entity
  437.      *
  438.      * @return void
  439.      *
  440.      * @throws \InvalidArgumentException
  441.      */
  442.     private function computeSingleEntityChangeSet($entity)
  443.     {
  444.         $state $this->getEntityState($entity);
  445.         if ($state !== self::STATE_MANAGED && $state !== self::STATE_REMOVED) {
  446.             throw new \InvalidArgumentException("Entity has to be managed or scheduled for removal for single computation " self::objToStr($entity));
  447.         }
  448.         $class $this->em->getClassMetadata(get_class($entity));
  449.         if ($state === self::STATE_MANAGED && $class->isChangeTrackingDeferredImplicit()) {
  450.             $this->persist($entity);
  451.         }
  452.         // Compute changes for INSERTed entities first. This must always happen even in this case.
  453.         $this->computeScheduleInsertsChangeSets();
  454.         if ($class->isReadOnly) {
  455.             return;
  456.         }
  457.         // Ignore uninitialized proxy objects
  458.         if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
  459.             return;
  460.         }
  461.         // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
  462.         $oid spl_object_hash($entity);
  463.         if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
  464.             $this->computeChangeSet($class$entity);
  465.         }
  466.     }
  467.     /**
  468.      * Executes any extra updates that have been scheduled.
  469.      */
  470.     private function executeExtraUpdates()
  471.     {
  472.         foreach ($this->extraUpdates as $oid => $update) {
  473.             [$entity$changeset] = $update;
  474.             $this->entityChangeSets[$oid] = $changeset;
  475.             $this->getEntityPersister(get_class($entity))->update($entity);
  476.         }
  477.         $this->extraUpdates = [];
  478.     }
  479.     /**
  480.      * Gets the changeset for an entity.
  481.      *
  482.      * @param object $entity
  483.      *
  484.      * @return array
  485.      */
  486.     public function & getEntityChangeSet($entity)
  487.     {
  488.         $oid  spl_object_hash($entity);
  489.         $data = [];
  490.         if (!isset($this->entityChangeSets[$oid])) {
  491.             return $data;
  492.         }
  493.         return $this->entityChangeSets[$oid];
  494.     }
  495.     /**
  496.      * Computes the changes that happened to a single entity.
  497.      *
  498.      * Modifies/populates the following properties:
  499.      *
  500.      * {@link _originalEntityData}
  501.      * If the entity is NEW or MANAGED but not yet fully persisted (only has an id)
  502.      * then it was not fetched from the database and therefore we have no original
  503.      * entity data yet. All of the current entity data is stored as the original entity data.
  504.      *
  505.      * {@link _entityChangeSets}
  506.      * The changes detected on all properties of the entity are stored there.
  507.      * A change is a tuple array where the first entry is the old value and the second
  508.      * entry is the new value of the property. Changesets are used by persisters
  509.      * to INSERT/UPDATE the persistent entity state.
  510.      *
  511.      * {@link _entityUpdates}
  512.      * If the entity is already fully MANAGED (has been fetched from the database before)
  513.      * and any changes to its properties are detected, then a reference to the entity is stored
  514.      * there to mark it for an update.
  515.      *
  516.      * {@link _collectionDeletions}
  517.      * If a PersistentCollection has been de-referenced in a fully MANAGED entity,
  518.      * then this collection is marked for deletion.
  519.      *
  520.      * @ignore
  521.      *
  522.      * @internal Don't call from the outside.
  523.      *
  524.      * @param ClassMetadata $class  The class descriptor of the entity.
  525.      * @param object        $entity The entity for which to compute the changes.
  526.      *
  527.      * @return void
  528.      */
  529.     public function computeChangeSet(ClassMetadata $class$entity)
  530.     {
  531.         $oid spl_object_hash($entity);
  532.         if (isset($this->readOnlyObjects[$oid])) {
  533.             return;
  534.         }
  535.         if ( ! $class->isInheritanceTypeNone()) {
  536.             $class $this->em->getClassMetadata(get_class($entity));
  537.         }
  538.         $invoke $this->listenersInvoker->getSubscribedSystems($classEvents::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;
  539.         if ($invoke !== ListenersInvoker::INVOKE_NONE) {
  540.             $this->listenersInvoker->invoke($classEvents::preFlush$entity, new PreFlushEventArgs($this->em), $invoke);
  541.         }
  542.         $actualData = [];
  543.         foreach ($class->reflFields as $name => $refProp) {
  544.             $value $refProp->getValue($entity);
  545.             if ($class->isCollectionValuedAssociation($name) && $value !== null) {
  546.                 if ($value instanceof PersistentCollection) {
  547.                     if ($value->getOwner() === $entity) {
  548.                         continue;
  549.                     }
  550.                     $value = new ArrayCollection($value->getValues());
  551.                 }
  552.                 // If $value is not a Collection then use an ArrayCollection.
  553.                 if ( ! $value instanceof Collection) {
  554.                     $value = new ArrayCollection($value);
  555.                 }
  556.                 $assoc $class->associationMappings[$name];
  557.                 // Inject PersistentCollection
  558.                 $value = new PersistentCollection(
  559.                     $this->em$this->em->getClassMetadata($assoc['targetEntity']), $value
  560.                 );
  561.                 $value->setOwner($entity$assoc);
  562.                 $value->setDirty( ! $value->isEmpty());
  563.                 $class->reflFields[$name]->setValue($entity$value);
  564.                 $actualData[$name] = $value;
  565.                 continue;
  566.             }
  567.             if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) {
  568.                 $actualData[$name] = $value;
  569.             }
  570.         }
  571. // // LG 20230821 pour tests
  572. // if ( get_class($entity) == "App\PaaBundle\Entity\matieres" || get_class($entity) == "Proxies\__CG__\App\PaaBundle\Entity\actibases" ) {
  573. //     $toto = "toto" ;
  574. // }
  575.         if ( ! isset($this->originalEntityData[$oid])) {
  576.             // Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
  577.             // These result in an INSERT.
  578.             $this->originalEntityData[$oid] = $actualData;
  579.             $changeSet = [];
  580.             foreach ($actualData as $propName => $actualValue) {
  581.                 if ( ! isset($class->associationMappings[$propName])) {
  582.                     $changeSet[$propName] = [null$actualValue];
  583.                     continue;
  584.                 }
  585.                 $assoc $class->associationMappings[$propName];
  586.                 if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
  587.                     $changeSet[$propName] = [null$actualValue];
  588.                 }
  589.             }
  590.             $this->entityChangeSets[$oid] = $changeSet;
  591.         } else {
  592.             // Entity is "fully" MANAGED: it was already fully persisted before
  593.             // and we have a copy of the original data
  594.             $originalData           $this->originalEntityData[$oid];
  595.             $isChangeTrackingNotify $class->isChangeTrackingNotify();
  596.             $changeSet              = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid]))
  597.                 ? $this->entityChangeSets[$oid]
  598.                 : [];
  599.             foreach ($actualData as $propName => $actualValue) {
  600.                 // skip field, its a partially omitted one!
  601.                 if ( ! (isset($originalData[$propName]) || array_key_exists($propName$originalData))) {
  602.                     continue;
  603.                 }
  604.                 $orgValue $originalData[$propName];
  605.                 // skip if value haven't changed
  606.                 if ($orgValue === $actualValue) {
  607.                     continue;
  608.                 }
  609.                 // if regular field
  610.                 if ( ! isset($class->associationMappings[$propName])) {
  611.                     if ($isChangeTrackingNotify) {
  612.                         continue;
  613.                     }
  614.                     $changeSet[$propName] = [$orgValue$actualValue];
  615.                     continue;
  616.                 }
  617.                 $assoc $class->associationMappings[$propName];
  618.                 // Persistent collection was exchanged with the "originally"
  619.                 // created one. This can only mean it was cloned and replaced
  620.                 // on another entity.
  621.                 if ($actualValue instanceof PersistentCollection) {
  622.                     $owner $actualValue->getOwner();
  623.                     if ($owner === null) { // cloned
  624.                         $actualValue->setOwner($entity$assoc);
  625.                     } else if ($owner !== $entity) { // no clone, we have to fix
  626.                         if (!$actualValue->isInitialized()) {
  627.                             $actualValue->initialize(); // we have to do this otherwise the cols share state
  628.                         }
  629.                         $newValue = clone $actualValue;
  630.                         $newValue->setOwner($entity$assoc);
  631.                         $class->reflFields[$propName]->setValue($entity$newValue);
  632.                     }
  633.                 }
  634.                 if ($orgValue instanceof PersistentCollection) {
  635.                     // A PersistentCollection was de-referenced, so delete it.
  636.                     $coid spl_object_hash($orgValue);
  637.                     if (isset($this->collectionDeletions[$coid])) {
  638.                         continue;
  639.                     }
  640.                     $this->collectionDeletions[$coid] = $orgValue;
  641.                     $changeSet[$propName] = $orgValue// Signal changeset, to-many assocs will be ignored.
  642.                     continue;
  643.                 }
  644.                 if ($assoc['type'] & ClassMetadata::TO_ONE) {
  645.                     if ($assoc['isOwningSide']) {
  646.                         $changeSet[$propName] = [$orgValue$actualValue];
  647.                     }
  648.                     if ($orgValue !== null && $assoc['orphanRemoval']) {
  649.                         $this->scheduleOrphanRemoval($orgValue);
  650.                     }
  651.                 }
  652.             }
  653.             if ($changeSet) {
  654.                 $this->entityChangeSets[$oid]   = $changeSet;
  655.                 $this->originalEntityData[$oid] = $actualData;
  656.                 $this->entityUpdates[$oid]      = $entity;
  657.             }
  658.         }
  659.         // Look for changes in associations of the entity
  660.         foreach ($class->associationMappings as $field => $assoc) {
  661.             if (($val $class->reflFields[$field]->getValue($entity)) === null) {
  662.                 continue;
  663.             }
  664.             $this->computeAssociationChanges($assoc$val);
  665.             if ( ! isset($this->entityChangeSets[$oid]) &&
  666.                 $assoc['isOwningSide'] &&
  667.                 $assoc['type'] == ClassMetadata::MANY_TO_MANY &&
  668.                 $val instanceof PersistentCollection &&
  669.                 $val->isDirty()) {
  670.                 $this->entityChangeSets[$oid]   = [];
  671.                 $this->originalEntityData[$oid] = $actualData;
  672.                 $this->entityUpdates[$oid]      = $entity;
  673.             }
  674.         }
  675.     }
  676.     /**
  677.      * Computes all the changes that have been done to entities and collections
  678.      * since the last commit and stores these changes in the _entityChangeSet map
  679.      * temporarily for access by the persisters, until the UoW commit is finished.
  680.      *
  681.      * @return void
  682.      */
  683.     public function computeChangeSets()
  684.     {
  685.         // Compute changes for INSERTed entities first. This must always happen.
  686.         $this->computeScheduleInsertsChangeSets();
  687.         // Compute changes for other MANAGED entities. Change tracking policies take effect here.
  688.         foreach ($this->identityMap as $className => $entities) {
  689.             $class $this->em->getClassMetadata($className);
  690.             // Skip class if instances are read-only
  691.             if ($class->isReadOnly) {
  692.                 continue;
  693.             }
  694.             // If change tracking is explicit or happens through notification, then only compute
  695.             // changes on entities of that type that are explicitly marked for synchronization.
  696.             switch (true) {
  697.                 case ($class->isChangeTrackingDeferredImplicit()):
  698.                     $entitiesToProcess $entities;
  699.                     break;
  700.                 case (isset($this->scheduledForSynchronization[$className])):
  701.                     $entitiesToProcess $this->scheduledForSynchronization[$className];
  702.                     break;
  703.                 default:
  704.                     $entitiesToProcess = [];
  705.             }
  706.             foreach ($entitiesToProcess as $entity) {
  707.                 // Ignore uninitialized proxy objects
  708.                 if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
  709.                     continue;
  710.                 }
  711.                 // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
  712.                 $oid spl_object_hash($entity);
  713.                 if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
  714. // if ( get_class($entity) == "App\PaaBundle\Entity\matieres" || get_class($entity) == "Proxies\__CG__\App\PaaBundle\Entity\actibases" ) {
  715. //     $toto = "toto" ;
  716. // }                    
  717.                     $this->computeChangeSet($class$entity);
  718.                 }
  719.             }
  720.         }
  721.     }
  722.     /**
  723.      * Computes the changes of an association.
  724.      *
  725.      * @param array $assoc The association mapping.
  726.      * @param mixed $value The value of the association.
  727.      *
  728.      * @throws ORMInvalidArgumentException
  729.      * @throws ORMException
  730.      *
  731.      * @return void
  732.      */
  733.     private function computeAssociationChanges($assoc$value)
  734.     {
  735.         if ($value instanceof Proxy && ! $value->__isInitialized__) {
  736.             return;
  737.         }
  738.         if ($value instanceof PersistentCollection && $value->isDirty()) {
  739.             $coid spl_object_hash($value);
  740.             $this->collectionUpdates[$coid] = $value;
  741.             $this->visitedCollections[$coid] = $value;
  742.         }
  743.         // Look through the entities, and in any of their associations,
  744.         // for transient (new) entities, recursively. ("Persistence by reachability")
  745.         // Unwrap. Uninitialized collections will simply be empty.
  746.         $unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? [$value] : $value->unwrap();
  747.         $targetClass    $this->em->getClassMetadata($assoc['targetEntity']);
  748.         foreach ($unwrappedValue as $key => $entry) {
  749.             if (! ($entry instanceof $targetClass->name)) {
  750.                 throw ORMInvalidArgumentException::invalidAssociation($targetClass$assoc$entry);
  751.             }
  752.             $state $this->getEntityState($entryself::STATE_NEW);
  753.             if ( ! ($entry instanceof $assoc['targetEntity'])) {
  754.                 throw ORMException::unexpectedAssociationValue($assoc['sourceEntity'], $assoc['fieldName'], get_class($entry), $assoc['targetEntity']);
  755.             }
  756.             switch ($state) {
  757.                 case self::STATE_NEW:
  758.                     if ( ! $assoc['isCascadePersist']) {
  759.                         /*
  760.                          * For now just record the details, because this may
  761.                          * not be an issue if we later discover another pathway
  762.                          * through the object-graph where cascade-persistence
  763.                          * is enabled for this object.
  764.                          */
  765.                         $this->nonCascadedNewDetectedEntities[spl_object_hash($entry)] = [$assoc$entry];
  766.                         break;
  767.                     }
  768.                     $this->persistNew($targetClass$entry);
  769.                     $this->computeChangeSet($targetClass$entry);
  770.                     break;
  771.                 case self::STATE_REMOVED:
  772.                     // Consume the $value as array (it's either an array or an ArrayAccess)
  773.                     // and remove the element from Collection.
  774.                     if ($assoc['type'] & ClassMetadata::TO_MANY) {
  775.                         unset($value[$key]);
  776.                     }
  777.                     break;
  778.                 case self::STATE_DETACHED:
  779.                     // Can actually not happen right now as we assume STATE_NEW,
  780.                     // so the exception will be raised from the DBAL layer (constraint violation).
  781.                     throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($assoc$entry);
  782.                     break;
  783.                 default:
  784.                     // MANAGED associated entities are already taken into account
  785.                     // during changeset calculation anyway, since they are in the identity map.
  786.             }
  787.         }
  788.     }
  789.     /**
  790.      * @param \Doctrine\ORM\Mapping\ClassMetadata $class
  791.      * @param object                              $entity
  792.      *
  793.      * @return void
  794.      */
  795.     private function persistNew($class$entity)
  796.     {
  797.         $oid    spl_object_hash($entity);
  798.         $invoke $this->listenersInvoker->getSubscribedSystems($classEvents::prePersist);
  799.         if ($invoke !== ListenersInvoker::INVOKE_NONE) {
  800.             $this->listenersInvoker->invoke($classEvents::prePersist$entity, new LifecycleEventArgs($entity$this->em), $invoke);
  801.         }
  802.         $idGen $class->idGenerator;
  803.         if ( ! $idGen->isPostInsertGenerator()) {
  804.             $idValue $idGen->generate($this->em$entity);
  805.             if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) {
  806.                 $idValue = [$class->getSingleIdentifierFieldName() => $this->convertSingleFieldIdentifierToPHPValue($class$idValue)];
  807.                 $class->setIdentifierValues($entity$idValue);
  808.             }
  809.             // Some identifiers may be foreign keys to new entities.
  810.             // In this case, we don't have the value yet and should treat it as if we have a post-insert generator
  811.             if (! $this->hasMissingIdsWhichAreForeignKeys($class$idValue)) {
  812.                 $this->entityIdentifiers[$oid] = $idValue;
  813.             }
  814.         }
  815.         $this->entityStates[$oid] = self::STATE_MANAGED;
  816.         $this->scheduleForInsert($entity);
  817.     }
  818.     /**
  819.      * @param mixed[] $idValue
  820.      */
  821.     private function hasMissingIdsWhichAreForeignKeys(ClassMetadata $class, array $idValue) : bool
  822.     {
  823.         foreach ($idValue as $idField => $idFieldValue) {
  824.             if ($idFieldValue === null && isset($class->associationMappings[$idField])) {
  825.                 return true;
  826.             }
  827.         }
  828.         return false;
  829.     }
  830.     /**
  831.      * INTERNAL:
  832.      * Computes the changeset of an individual entity, independently of the
  833.      * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
  834.      *
  835.      * The passed entity must be a managed entity. If the entity already has a change set
  836.      * because this method is invoked during a commit cycle then the change sets are added.
  837.      * whereby changes detected in this method prevail.
  838.      *
  839.      * @ignore
  840.      *
  841.      * @param ClassMetadata $class  The class descriptor of the entity.
  842.      * @param object        $entity The entity for which to (re)calculate the change set.
  843.      *
  844.      * @return void
  845.      *
  846.      * @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
  847.      */
  848.     public function recomputeSingleEntityChangeSet(ClassMetadata $class$entity)
  849.     {
  850.         $oid spl_object_hash($entity);
  851.         if ( ! isset($this->entityStates[$oid]) || $this->entityStates[$oid] != self::STATE_MANAGED) {
  852.             throw ORMInvalidArgumentException::entityNotManaged($entity);
  853.         }
  854.         // skip if change tracking is "NOTIFY"
  855.         if ($class->isChangeTrackingNotify()) {
  856.             return;
  857.         }
  858.         if ( ! $class->isInheritanceTypeNone()) {
  859.             $class $this->em->getClassMetadata(get_class($entity));
  860.         }
  861.         $actualData = [];
  862.         foreach ($class->reflFields as $name => $refProp) {
  863.             if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity())
  864.                 && ($name !== $class->versionField)
  865.                 && ! $class->isCollectionValuedAssociation($name)) {
  866.                 $actualData[$name] = $refProp->getValue($entity);
  867.             }
  868.         }
  869.         if ( ! isset($this->originalEntityData[$oid])) {
  870.             throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
  871.         }
  872.         $originalData $this->originalEntityData[$oid];
  873.         $changeSet = [];
  874.         foreach ($actualData as $propName => $actualValue) {
  875.             $orgValue $originalData[$propName] ?? null;
  876.             if ($orgValue !== $actualValue) {
  877.                 $changeSet[$propName] = [$orgValue$actualValue];
  878.             }
  879.         }
  880.         if ($changeSet) {
  881.             if (isset($this->entityChangeSets[$oid])) {
  882.                 $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
  883.             } else if ( ! isset($this->entityInsertions[$oid])) {
  884.                 $this->entityChangeSets[$oid] = $changeSet;
  885.                 $this->entityUpdates[$oid]    = $entity;
  886.             }
  887.             $this->originalEntityData[$oid] = $actualData;
  888.         }
  889.     }
  890.     /**
  891.      * Executes all entity insertions for entities of the specified type.
  892.      *
  893.      * @param \Doctrine\ORM\Mapping\ClassMetadata $class
  894.      *
  895.      * @return void
  896.      */
  897.     private function executeInserts($class)
  898.     {
  899.         $entities   = [];
  900.         $className  $class->name;
  901.         $persister  $this->getEntityPersister($className);
  902.         $invoke     $this->listenersInvoker->getSubscribedSystems($classEvents::postPersist);
  903.         $insertionsForClass = [];
  904.         foreach ($this->entityInsertions as $oid => $entity) {
  905.             if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
  906.                 continue;
  907.             }
  908.             $insertionsForClass[$oid] = $entity;
  909.             $persister->addInsert($entity);
  910.             unset($this->entityInsertions[$oid]);
  911.             if ($invoke !== ListenersInvoker::INVOKE_NONE) {
  912.                 $entities[] = $entity;
  913.             }
  914.         }
  915.         $postInsertIds $persister->executeInserts();
  916.         if ($postInsertIds) {
  917.             // Persister returned post-insert IDs
  918.             foreach ($postInsertIds as $postInsertId) {
  919.                 $idField $class->getSingleIdentifierFieldName();
  920.                 $idValue $this->convertSingleFieldIdentifierToPHPValue($class$postInsertId['generatedId']);
  921.                 $entity  $postInsertId['entity'];
  922.                 $oid     spl_object_hash($entity);
  923.                 $class->reflFields[$idField]->setValue($entity$idValue);
  924.                 $this->entityIdentifiers[$oid] = [$idField => $idValue];
  925.                 $this->entityStates[$oid] = self::STATE_MANAGED;
  926.                 $this->originalEntityData[$oid][$idField] = $idValue;
  927.                 $this->addToIdentityMap($entity);
  928.             }
  929.         } else {
  930.             foreach ($insertionsForClass as $oid => $entity) {
  931.                 if (! isset($this->entityIdentifiers[$oid])) {
  932.                     //entity was not added to identity map because some identifiers are foreign keys to new entities.
  933.                     //add it now
  934.                     $this->addToEntityIdentifiersAndEntityMap($class$oid$entity);
  935.                 }
  936.             }
  937.         }
  938.         foreach ($entities as $entity) {
  939.             $this->listenersInvoker->invoke($classEvents::postPersist$entity, new LifecycleEventArgs($entity$this->em), $invoke);
  940.         }
  941.     }
  942.     /**
  943.      * @param object $entity
  944.      */
  945.     private function addToEntityIdentifiersAndEntityMap(ClassMetadata $classstring $oid$entity): void
  946.     {
  947.         $identifier = [];
  948.         foreach ($class->getIdentifierFieldNames() as $idField) {
  949.             $value $class->getFieldValue($entity$idField);
  950.             if (isset($class->associationMappings[$idField])) {
  951.                 // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
  952.                 $value $this->getSingleIdentifierValue($value);
  953.             }
  954.             $identifier[$idField] = $this->originalEntityData[$oid][$idField] = $value;
  955.         }
  956.         $this->entityStates[$oid]      = self::STATE_MANAGED;
  957.         $this->entityIdentifiers[$oid] = $identifier;
  958.         $this->addToIdentityMap($entity);
  959.     }
  960.     /**
  961.      * Executes all entity updates for entities of the specified type.
  962.      *
  963.      * @param \Doctrine\ORM\Mapping\ClassMetadata $class
  964.      *
  965.      * @return void
  966.      */
  967.     private function executeUpdates($class)
  968.     {
  969.         $className          $class->name;
  970.         $persister          $this->getEntityPersister($className);
  971.         $preUpdateInvoke    $this->listenersInvoker->getSubscribedSystems($classEvents::preUpdate);
  972.         $postUpdateInvoke   $this->listenersInvoker->getSubscribedSystems($classEvents::postUpdate);
  973.         foreach ($this->entityUpdates as $oid => $entity) {
  974.             if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
  975.                 continue;
  976.             }
  977.             if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
  978.                 $this->listenersInvoker->invoke($classEvents::preUpdate$entity, new PreUpdateEventArgs($entity$this->em$this->getEntityChangeSet($entity)), $preUpdateInvoke);
  979.                 $this->recomputeSingleEntityChangeSet($class$entity);
  980.             }
  981.             if ( ! empty($this->entityChangeSets[$oid])) {
  982.                 $persister->update($entity);
  983.             }
  984.             unset($this->entityUpdates[$oid]);
  985.             if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
  986.                 $this->listenersInvoker->invoke($classEvents::postUpdate$entity, new LifecycleEventArgs($entity$this->em), $postUpdateInvoke);
  987.             }
  988.         }
  989.     }
  990.     /**
  991.      * Executes all entity deletions for entities of the specified type.
  992.      *
  993.      * @param \Doctrine\ORM\Mapping\ClassMetadata $class
  994.      *
  995.      * @return void
  996.      */
  997.     private function executeDeletions($class)
  998.     {
  999.         $className  $class->name;
  1000.         $persister  $this->getEntityPersister($className);
  1001.         $invoke     $this->listenersInvoker->getSubscribedSystems($classEvents::postRemove);
  1002.         foreach ($this->entityDeletions as $oid => $entity) {
  1003.             if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
  1004.                 continue;
  1005.             }
  1006.             $persister->delete($entity);
  1007.             unset(
  1008.                 $this->entityDeletions[$oid],
  1009.                 $this->entityIdentifiers[$oid],
  1010.                 $this->originalEntityData[$oid],
  1011.                 $this->entityStates[$oid]
  1012.             );
  1013.             // Entity with this $oid after deletion treated as NEW, even if the $oid
  1014.             // is obtained by a new entity because the old one went out of scope.
  1015.             //$this->entityStates[$oid] = self::STATE_NEW;
  1016.             if ( ! $class->isIdentifierNatural()) {
  1017.                 $class->reflFields[$class->identifier[0]]->setValue($entitynull);
  1018.             }
  1019.             if ($invoke !== ListenersInvoker::INVOKE_NONE) {
  1020.                 $this->listenersInvoker->invoke($classEvents::postRemove$entity, new LifecycleEventArgs($entity$this->em), $invoke);
  1021.             }
  1022.         }
  1023.     }
  1024.     /**
  1025.      * Gets the commit order.
  1026.      *
  1027.      * @param array|null $entityChangeSet
  1028.      *
  1029.      * @return array
  1030.      */
  1031.     private function getCommitOrder(array $entityChangeSet null)
  1032.     {
  1033.         if ($entityChangeSet === null) {
  1034.             $entityChangeSet array_merge($this->entityInsertions$this->entityUpdates$this->entityDeletions);
  1035.         }
  1036.         $calc $this->getCommitOrderCalculator();
  1037.         // See if there are any new classes in the changeset, that are not in the
  1038.         // commit order graph yet (don't have a node).
  1039.         // We have to inspect changeSet to be able to correctly build dependencies.
  1040.         // It is not possible to use IdentityMap here because post inserted ids
  1041.         // are not yet available.
  1042.         $newNodes = [];
  1043.         foreach ($entityChangeSet as $entity) {
  1044.             $class $this->em->getClassMetadata(get_class($entity));
  1045.             if ($calc->hasNode($class->name)) {
  1046.                 continue;
  1047.             }
  1048.             $calc->addNode($class->name$class);
  1049.             $newNodes[] = $class;
  1050.         }
  1051.         // Calculate dependencies for new nodes
  1052.         while ($class array_pop($newNodes)) {
  1053.             foreach ($class->associationMappings as $assoc) {
  1054.                 if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
  1055.                     continue;
  1056.                 }
  1057.                 $targetClass $this->em->getClassMetadata($assoc['targetEntity']);
  1058.                 if ( ! $calc->hasNode($targetClass->name)) {
  1059.                     $calc->addNode($targetClass->name$targetClass);
  1060.                     $newNodes[] = $targetClass;
  1061.                 }
  1062.                 $joinColumns reset($assoc['joinColumns']);
  1063.                 $calc->addDependency($targetClass->name$class->name, (int)empty($joinColumns['nullable']));
  1064.                 // If the target class has mapped subclasses, these share the same dependency.
  1065.                 if ( ! $targetClass->subClasses) {
  1066.                     continue;
  1067.                 }
  1068.                 foreach ($targetClass->subClasses as $subClassName) {
  1069.                     $targetSubClass $this->em->getClassMetadata($subClassName);
  1070.                     if ( ! $calc->hasNode($subClassName)) {
  1071.                         $calc->addNode($targetSubClass->name$targetSubClass);
  1072.                         $newNodes[] = $targetSubClass;
  1073.                     }
  1074.                     $calc->addDependency($targetSubClass->name$class->name1);
  1075.                 }
  1076.             }
  1077.         }
  1078.         return $calc->sort();
  1079.     }
  1080.     /**
  1081.      * Schedules an entity for insertion into the database.
  1082.      * If the entity already has an identifier, it will be added to the identity map.
  1083.      *
  1084.      * @param object $entity The entity to schedule for insertion.
  1085.      *
  1086.      * @return void
  1087.      *
  1088.      * @throws ORMInvalidArgumentException
  1089.      * @throws \InvalidArgumentException
  1090.      */
  1091.     public function scheduleForInsert($entity)
  1092.     {
  1093.         $oid spl_object_hash($entity);
  1094.         if (isset($this->entityUpdates[$oid])) {
  1095.             throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion.");
  1096.         }
  1097.         if (isset($this->entityDeletions[$oid])) {
  1098.             throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
  1099.         }
  1100.         if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
  1101.             throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
  1102.         }
  1103.         if (isset($this->entityInsertions[$oid])) {
  1104.             throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
  1105.         }
  1106.         $this->entityInsertions[$oid] = $entity;
  1107.         if (isset($this->entityIdentifiers[$oid])) {
  1108.             $this->addToIdentityMap($entity);
  1109.         }
  1110.         if ($entity instanceof NotifyPropertyChanged) {
  1111.             $entity->addPropertyChangedListener($this);
  1112.         }
  1113.     }
  1114.     /**
  1115.      * Checks whether an entity is scheduled for insertion.
  1116.      *
  1117.      * @param object $entity
  1118.      *
  1119.      * @return boolean
  1120.      */
  1121.     public function isScheduledForInsert($entity)
  1122.     {
  1123.         return isset($this->entityInsertions[spl_object_hash($entity)]);
  1124.     }
  1125.     /**
  1126.      * Schedules an entity for being updated.
  1127.      *
  1128.      * @param object $entity The entity to schedule for being updated.
  1129.      *
  1130.      * @return void
  1131.      *
  1132.      * @throws ORMInvalidArgumentException
  1133.      */
  1134.     public function scheduleForUpdate($entity)
  1135.     {
  1136.         $oid spl_object_hash($entity);
  1137.         if ( ! isset($this->entityIdentifiers[$oid])) {
  1138.             throw ORMInvalidArgumentException::entityHasNoIdentity($entity"scheduling for update");
  1139.         }
  1140.         if (isset($this->entityDeletions[$oid])) {
  1141.             throw ORMInvalidArgumentException::entityIsRemoved($entity"schedule for update");
  1142.         }
  1143.         if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
  1144.             $this->entityUpdates[$oid] = $entity;
  1145.         }
  1146.     }
  1147.     /**
  1148.      * INTERNAL:
  1149.      * Schedules an extra update that will be executed immediately after the
  1150.      * regular entity updates within the currently running commit cycle.
  1151.      *
  1152.      * Extra updates for entities are stored as (entity, changeset) tuples.
  1153.      *
  1154.      * @ignore
  1155.      *
  1156.      * @param object $entity    The entity for which to schedule an extra update.
  1157.      * @param array  $changeset The changeset of the entity (what to update).
  1158.      *
  1159.      * @return void
  1160.      */
  1161.     public function scheduleExtraUpdate($entity, array $changeset)
  1162.     {
  1163.         $oid         spl_object_hash($entity);
  1164.         $extraUpdate = [$entity$changeset];
  1165.         if (isset($this->extraUpdates[$oid])) {
  1166.             [, $changeset2] = $this->extraUpdates[$oid];
  1167.             $extraUpdate = [$entity$changeset $changeset2];
  1168.         }
  1169.         $this->extraUpdates[$oid] = $extraUpdate;
  1170.     }
  1171.     /**
  1172.      * Checks whether an entity is registered as dirty in the unit of work.
  1173.      * Note: Is not very useful currently as dirty entities are only registered
  1174.      * at commit time.
  1175.      *
  1176.      * @param object $entity
  1177.      *
  1178.      * @return boolean
  1179.      */
  1180.     public function isScheduledForUpdate($entity)
  1181.     {
  1182.         return isset($this->entityUpdates[spl_object_hash($entity)]);
  1183.     }
  1184.     /**
  1185.      * Checks whether an entity is registered to be checked in the unit of work.
  1186.      *
  1187.      * @param object $entity
  1188.      *
  1189.      * @return boolean
  1190.      */
  1191.     public function isScheduledForDirtyCheck($entity)
  1192.     {
  1193.         $rootEntityName $this->em->getClassMetadata(get_class($entity))->rootEntityName;
  1194.         return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_hash($entity)]);
  1195.     }
  1196.     /**
  1197.      * INTERNAL:
  1198.      * Schedules an entity for deletion.
  1199.      *
  1200.      * @param object $entity
  1201.      *
  1202.      * @return void
  1203.      */
  1204.     public function scheduleForDelete($entity)
  1205.     {
  1206.         $oid spl_object_hash($entity);
  1207.         if (isset($this->entityInsertions[$oid])) {
  1208.             if ($this->isInIdentityMap($entity)) {
  1209.                 $this->removeFromIdentityMap($entity);
  1210.             }
  1211.             unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
  1212.             return; // entity has not been persisted yet, so nothing more to do.
  1213.         }
  1214.         if ( ! $this->isInIdentityMap($entity)) {
  1215.             return;
  1216.         }
  1217.         $this->removeFromIdentityMap($entity);
  1218.         unset($this->entityUpdates[$oid]);
  1219.         if ( ! isset($this->entityDeletions[$oid])) {
  1220.             $this->entityDeletions[$oid] = $entity;
  1221.             $this->entityStates[$oid]    = self::STATE_REMOVED;
  1222.         }
  1223.     }
  1224.     /**
  1225.      * Checks whether an entity is registered as removed/deleted with the unit
  1226.      * of work.
  1227.      *
  1228.      * @param object $entity
  1229.      *
  1230.      * @return boolean
  1231.      */
  1232.     public function isScheduledForDelete($entity)
  1233.     {
  1234.         return isset($this->entityDeletions[spl_object_hash($entity)]);
  1235.     }
  1236.     /**
  1237.      * Checks whether an entity is scheduled for insertion, update or deletion.
  1238.      *
  1239.      * @param object $entity
  1240.      *
  1241.      * @return boolean
  1242.      */
  1243.     public function isEntityScheduled($entity)
  1244.     {
  1245.         $oid spl_object_hash($entity);
  1246.         return isset($this->entityInsertions[$oid])
  1247.             || isset($this->entityUpdates[$oid])
  1248.             || isset($this->entityDeletions[$oid]);
  1249.     }
  1250.     /**
  1251.      * INTERNAL:
  1252.      * Registers an entity in the identity map.
  1253.      * Note that entities in a hierarchy are registered with the class name of
  1254.      * the root entity.
  1255.      *
  1256.      * @ignore
  1257.      *
  1258.      * @param object $entity The entity to register.
  1259.      *
  1260.      * @return boolean TRUE if the registration was successful, FALSE if the identity of
  1261.      *                 the entity in question is already managed.
  1262.      *
  1263.      * @throws ORMInvalidArgumentException
  1264.      */
  1265.     public function addToIdentityMap($entity)
  1266.     {
  1267.         $classMetadata $this->em->getClassMetadata(get_class($entity));
  1268.         $identifier    $this->entityIdentifiers[spl_object_hash($entity)];
  1269.         if (empty($identifier) || in_array(null$identifiertrue)) {
  1270.             throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name$entity);
  1271.         }
  1272.         $idHash    implode(' '$identifier);
  1273.         $className $classMetadata->rootEntityName;
  1274.         if (isset($this->identityMap[$className][$idHash])) {
  1275.             return false;
  1276.         }
  1277.         $this->identityMap[$className][$idHash] = $entity;
  1278.         return true;
  1279.     }
  1280.     /**
  1281.      * Gets the state of an entity with regard to the current unit of work.
  1282.      *
  1283.      * @param object   $entity
  1284.      * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
  1285.      *                         This parameter can be set to improve performance of entity state detection
  1286.      *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
  1287.      *                         is either known or does not matter for the caller of the method.
  1288.      *
  1289.      * @return int The entity state.
  1290.      */
  1291.     public function getEntityState($entity$assume null)
  1292.     {
  1293.         $oid spl_object_hash($entity);
  1294.         if (isset($this->entityStates[$oid])) {
  1295.             return $this->entityStates[$oid];
  1296.         }
  1297.         if ($assume !== null) {
  1298.             return $assume;
  1299.         }
  1300.         // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
  1301.         // Note that you can not remember the NEW or DETACHED state in _entityStates since
  1302.         // the UoW does not hold references to such objects and the object hash can be reused.
  1303.         // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
  1304.         $class $this->em->getClassMetadata(get_class($entity));
  1305.         $id    $class->getIdentifierValues($entity);
  1306.         if ( ! $id) {
  1307.             return self::STATE_NEW;
  1308.         }
  1309.         if ($class->containsForeignIdentifier) {
  1310.             $id $this->identifierFlattener->flattenIdentifier($class$id);
  1311.         }
  1312.         switch (true) {
  1313.             case ($class->isIdentifierNatural()):
  1314.                 // Check for a version field, if available, to avoid a db lookup.
  1315.                 if ($class->isVersioned) {
  1316.                     return ($class->getFieldValue($entity$class->versionField))
  1317.                         ? self::STATE_DETACHED
  1318.                         self::STATE_NEW;
  1319.                 }
  1320.                 // Last try before db lookup: check the identity map.
  1321.                 if ($this->tryGetById($id$class->rootEntityName)) {
  1322.                     return self::STATE_DETACHED;
  1323.                 }
  1324.                 // db lookup
  1325.                 if ($this->getEntityPersister($class->name)->exists($entity)) {
  1326.                     return self::STATE_DETACHED;
  1327.                 }
  1328.                 return self::STATE_NEW;
  1329.             case ( ! $class->idGenerator->isPostInsertGenerator()):
  1330.                 // if we have a pre insert generator we can't be sure that having an id
  1331.                 // really means that the entity exists. We have to verify this through
  1332.                 // the last resort: a db lookup
  1333.                 // Last try before db lookup: check the identity map.
  1334.                 if ($this->tryGetById($id$class->rootEntityName)) {
  1335.                     return self::STATE_DETACHED;
  1336.                 }
  1337.                 // db lookup
  1338.                 if ($this->getEntityPersister($class->name)->exists($entity)) {
  1339.                     return self::STATE_DETACHED;
  1340.                 }
  1341.                 return self::STATE_NEW;
  1342.             default:
  1343.                 return self::STATE_DETACHED;
  1344.         }
  1345.     }
  1346.     /**
  1347.      * INTERNAL:
  1348.      * Removes an entity from the identity map. This effectively detaches the
  1349.      * entity from the persistence management of Doctrine.
  1350.      *
  1351.      * @ignore
  1352.      *
  1353.      * @param object $entity
  1354.      *
  1355.      * @return boolean
  1356.      *
  1357.      * @throws ORMInvalidArgumentException
  1358.      */
  1359.     public function removeFromIdentityMap($entity)
  1360.     {
  1361.         $oid           spl_object_hash($entity);
  1362.         $classMetadata $this->em->getClassMetadata(get_class($entity));
  1363.         $idHash        implode(' '$this->entityIdentifiers[$oid]);
  1364.         if ($idHash === '') {
  1365.             throw ORMInvalidArgumentException::entityHasNoIdentity($entity"remove from identity map");
  1366.         }
  1367.         $className $classMetadata->rootEntityName;
  1368.         if (isset($this->identityMap[$className][$idHash])) {
  1369.             unset($this->identityMap[$className][$idHash]);
  1370.             unset($this->readOnlyObjects[$oid]);
  1371.             //$this->entityStates[$oid] = self::STATE_DETACHED;
  1372.             return true;
  1373.         }
  1374.         return false;
  1375.     }
  1376.     /**
  1377.      * INTERNAL:
  1378.      * Gets an entity in the identity map by its identifier hash.
  1379.      *
  1380.      * @ignore
  1381.      *
  1382.      * @param string $idHash
  1383.      * @param string $rootClassName
  1384.      *
  1385.      * @return object
  1386.      */
  1387.     public function getByIdHash($idHash$rootClassName)
  1388.     {
  1389.         return $this->identityMap[$rootClassName][$idHash];
  1390.     }
  1391.     /**
  1392.      * INTERNAL:
  1393.      * Tries to get an entity by its identifier hash. If no entity is found for
  1394.      * the given hash, FALSE is returned.
  1395.      *
  1396.      * @ignore
  1397.      *
  1398.      * @param mixed  $idHash        (must be possible to cast it to string)
  1399.      * @param string $rootClassName
  1400.      *
  1401.      * @return object|bool The found entity or FALSE.
  1402.      */
  1403.     public function tryGetByIdHash($idHash$rootClassName)
  1404.     {
  1405.         $stringIdHash = (string) $idHash;
  1406.         return isset($this->identityMap[$rootClassName][$stringIdHash])
  1407.             ? $this->identityMap[$rootClassName][$stringIdHash]
  1408.             : false;
  1409.     }
  1410.     /**
  1411.      * Checks whether an entity is registered in the identity map of this UnitOfWork.
  1412.      *
  1413.      * @param object $entity
  1414.      *
  1415.      * @return boolean
  1416.      */
  1417.     public function isInIdentityMap($entity)
  1418.     {
  1419.         $oid spl_object_hash($entity);
  1420.         if (empty($this->entityIdentifiers[$oid])) {
  1421.             return false;
  1422.         }
  1423.         $classMetadata $this->em->getClassMetadata(get_class($entity));
  1424.         $idHash        implode(' '$this->entityIdentifiers[$oid]);
  1425.         return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]);
  1426.     }
  1427.     /**
  1428.      * INTERNAL:
  1429.      * Checks whether an identifier hash exists in the identity map.
  1430.      *
  1431.      * @ignore
  1432.      *
  1433.      * @param string $idHash
  1434.      * @param string $rootClassName
  1435.      *
  1436.      * @return boolean
  1437.      */
  1438.     public function containsIdHash($idHash$rootClassName)
  1439.     {
  1440.         return isset($this->identityMap[$rootClassName][$idHash]);
  1441.     }
  1442.     /**
  1443.      * Persists an entity as part of the current unit of work.
  1444.      *
  1445.      * @param object $entity The entity to persist.
  1446.      *
  1447.      * @return void
  1448.      */
  1449.     public function persist($entity)
  1450.     {
  1451.         $visited = [];
  1452.         $this->doPersist($entity$visited);
  1453.     }
  1454.     /**
  1455.      * Persists an entity as part of the current unit of work.
  1456.      *
  1457.      * This method is internally called during persist() cascades as it tracks
  1458.      * the already visited entities to prevent infinite recursions.
  1459.      *
  1460.      * @param object $entity  The entity to persist.
  1461.      * @param array  $visited The already visited entities.
  1462.      *
  1463.      * @return void
  1464.      *
  1465.      * @throws ORMInvalidArgumentException
  1466.      * @throws UnexpectedValueException
  1467.      */
  1468.     private function doPersist($entity, array &$visited)
  1469.     {
  1470.         $oid spl_object_hash($entity);
  1471.         if (isset($visited[$oid])) {
  1472.             return; // Prevent infinite recursion
  1473.         }
  1474.         $visited[$oid] = $entity// Mark visited
  1475.         $class $this->em->getClassMetadata(get_class($entity));
  1476.         // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation).
  1477.         // If we would detect DETACHED here we would throw an exception anyway with the same
  1478.         // consequences (not recoverable/programming error), so just assuming NEW here
  1479.         // lets us avoid some database lookups for entities with natural identifiers.
  1480.         $entityState $this->getEntityState($entityself::STATE_NEW);
  1481.         switch ($entityState) {
  1482.             case self::STATE_MANAGED:
  1483.                 // Nothing to do, except if policy is "deferred explicit"
  1484.                 if ($class->isChangeTrackingDeferredExplicit()) {
  1485.                     $this->scheduleForDirtyCheck($entity);
  1486.                 }
  1487.                 break;
  1488.             case self::STATE_NEW:
  1489.                 $this->persistNew($class$entity);
  1490.                 break;
  1491.             case self::STATE_REMOVED:
  1492.                 // Entity becomes managed again
  1493.                 unset($this->entityDeletions[$oid]);
  1494.                 $this->addToIdentityMap($entity);
  1495.                 $this->entityStates[$oid] = self::STATE_MANAGED;
  1496.                 break;
  1497.             case self::STATE_DETACHED:
  1498.                 // Can actually not happen right now since we assume STATE_NEW.
  1499.                 throw ORMInvalidArgumentException::detachedEntityCannot($entity"persisted");
  1500.             default:
  1501.                 throw new UnexpectedValueException("Unexpected entity state: $entityState." self::objToStr($entity));
  1502.         }
  1503.         $this->cascadePersist($entity$visited);
  1504.     }
  1505.     /**
  1506.      * Deletes an entity as part of the current unit of work.
  1507.      *
  1508.      * @param object $entity The entity to remove.
  1509.      *
  1510.      * @return void
  1511.      */
  1512.     public function remove($entity)
  1513.     {
  1514.         $visited = [];
  1515.         $this->doRemove($entity$visited);
  1516.     }
  1517.     /**
  1518.      * Deletes an entity as part of the current unit of work.
  1519.      *
  1520.      * This method is internally called during delete() cascades as it tracks
  1521.      * the already visited entities to prevent infinite recursions.
  1522.      *
  1523.      * @param object $entity  The entity to delete.
  1524.      * @param array  $visited The map of the already visited entities.
  1525.      *
  1526.      * @return void
  1527.      *
  1528.      * @throws ORMInvalidArgumentException If the instance is a detached entity.
  1529.      * @throws UnexpectedValueException
  1530.      */
  1531.     private function doRemove($entity, array &$visited)
  1532.     {
  1533.         $oid spl_object_hash($entity);
  1534.         if (isset($visited[$oid])) {
  1535.             return; // Prevent infinite recursion
  1536.         }
  1537.         $visited[$oid] = $entity// mark visited
  1538.         // Cascade first, because scheduleForDelete() removes the entity from the identity map, which
  1539.         // can cause problems when a lazy proxy has to be initialized for the cascade operation.
  1540.         $this->cascadeRemove($entity$visited);
  1541.         $class       $this->em->getClassMetadata(get_class($entity));
  1542.         $entityState $this->getEntityState($entity);
  1543.         switch ($entityState) {
  1544.             case self::STATE_NEW:
  1545.             case self::STATE_REMOVED:
  1546.                 // nothing to do
  1547.                 break;
  1548.             case self::STATE_MANAGED:
  1549.                 $invoke $this->listenersInvoker->getSubscribedSystems($classEvents::preRemove);
  1550.                 if ($invoke !== ListenersInvoker::INVOKE_NONE) {
  1551.                     $this->listenersInvoker->invoke($classEvents::preRemove$entity, new LifecycleEventArgs($entity$this->em), $invoke);
  1552.                 }
  1553.                 $this->scheduleForDelete($entity);
  1554.                 break;
  1555.             case self::STATE_DETACHED:
  1556.                 throw ORMInvalidArgumentException::detachedEntityCannot($entity"removed");
  1557.             default:
  1558.                 throw new UnexpectedValueException("Unexpected entity state: $entityState." self::objToStr($entity));
  1559.         }
  1560.     }
  1561.     /**
  1562.      * Merges the state of the given detached entity into this UnitOfWork.
  1563.      *
  1564.      * @param object $entity
  1565.      *
  1566.      * @return object The managed copy of the entity.
  1567.      *
  1568.      * @throws OptimisticLockException If the entity uses optimistic locking through a version
  1569.      *         attribute and the version check against the managed copy fails.
  1570.      *
  1571.      * @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
  1572.      */
  1573.     public function merge($entity)
  1574.     {
  1575.         $visited = [];
  1576.         return $this->doMerge($entity$visited);
  1577.     }
  1578.     /**
  1579.      * Executes a merge operation on an entity.
  1580.      *
  1581.      * @param object      $entity
  1582.      * @param array       $visited
  1583.      * @param object|null $prevManagedCopy
  1584.      * @param string[]    $assoc
  1585.      *
  1586.      * @return object The managed copy of the entity.
  1587.      *
  1588.      * @throws OptimisticLockException If the entity uses optimistic locking through a version
  1589.      *         attribute and the version check against the managed copy fails.
  1590.      * @throws ORMInvalidArgumentException If the entity instance is NEW.
  1591.      * @throws EntityNotFoundException if an assigned identifier is used in the entity, but none is provided
  1592.      */
  1593.     private function doMerge($entity, array &$visited$prevManagedCopy null, array $assoc = [])
  1594.     {
  1595.         $oid spl_object_hash($entity);
  1596.         if (isset($visited[$oid])) {
  1597.             $managedCopy $visited[$oid];
  1598.             if ($prevManagedCopy !== null) {
  1599.                 $this->updateAssociationWithMergedEntity($entity$assoc$prevManagedCopy$managedCopy);
  1600.             }
  1601.             return $managedCopy;
  1602.         }
  1603.         $class $this->em->getClassMetadata(get_class($entity));
  1604.         // First we assume DETACHED, although it can still be NEW but we can avoid
  1605.         // an extra db-roundtrip this way. If it is not MANAGED but has an identity,
  1606.         // we need to fetch it from the db anyway in order to merge.
  1607.         // MANAGED entities are ignored by the merge operation.
  1608.         $managedCopy $entity;
  1609.         if ($this->getEntityState($entityself::STATE_DETACHED) !== self::STATE_MANAGED) {
  1610.             // Try to look the entity up in the identity map.
  1611.             $id $class->getIdentifierValues($entity);
  1612.             // If there is no ID, it is actually NEW.
  1613.             if ( ! $id) {
  1614.                 $managedCopy $this->newInstance($class);
  1615.                 $this->mergeEntityStateIntoManagedCopy($entity$managedCopy);
  1616.                 $this->persistNew($class$managedCopy);
  1617.             } else {
  1618.                 $flatId = ($class->containsForeignIdentifier)
  1619.                     ? $this->identifierFlattener->flattenIdentifier($class$id)
  1620.                     : $id;
  1621.                 $managedCopy $this->tryGetById($flatId$class->rootEntityName);
  1622.                 if ($managedCopy) {
  1623.                     // We have the entity in-memory already, just make sure its not removed.
  1624.                     if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) {
  1625.                         throw ORMInvalidArgumentException::entityIsRemoved($managedCopy"merge");
  1626.                     }
  1627.                 } else {
  1628.                     // We need to fetch the managed copy in order to merge.
  1629.                     $managedCopy $this->em->find($class->name$flatId);
  1630.                 }
  1631.                 if ($managedCopy === null) {
  1632.                     // If the identifier is ASSIGNED, it is NEW, otherwise an error
  1633.                     // since the managed entity was not found.
  1634.                     if ( ! $class->isIdentifierNatural()) {
  1635.                         throw EntityNotFoundException::fromClassNameAndIdentifier(
  1636.                             $class->getName(),
  1637.                             $this->identifierFlattener->flattenIdentifier($class$id)
  1638.                         );
  1639.                     }
  1640.                     $managedCopy $this->newInstance($class);
  1641.                     $class->setIdentifierValues($managedCopy$id);
  1642.                     $this->mergeEntityStateIntoManagedCopy($entity$managedCopy);
  1643.                     $this->persistNew($class$managedCopy);
  1644.                 } else {
  1645.                     $this->ensureVersionMatch($class$entity$managedCopy);
  1646.                     $this->mergeEntityStateIntoManagedCopy($entity$managedCopy);
  1647.                 }
  1648.             }
  1649.             $visited[$oid] = $managedCopy// mark visited
  1650.             if ($class->isChangeTrackingDeferredExplicit()) {
  1651.                 $this->scheduleForDirtyCheck($entity);
  1652.             }
  1653.         }
  1654.         if ($prevManagedCopy !== null) {
  1655.             $this->updateAssociationWithMergedEntity($entity$assoc$prevManagedCopy$managedCopy);
  1656.         }
  1657.         // Mark the managed copy visited as well
  1658.         $visited[spl_object_hash($managedCopy)] = $managedCopy;
  1659.         $this->cascadeMerge($entity$managedCopy$visited);
  1660.         return $managedCopy;
  1661.     }
  1662.     /**
  1663.      * @param ClassMetadata $class
  1664.      * @param object        $entity
  1665.      * @param object        $managedCopy
  1666.      *
  1667.      * @return void
  1668.      *
  1669.      * @throws OptimisticLockException
  1670.      */
  1671.     private function ensureVersionMatch(ClassMetadata $class$entity$managedCopy)
  1672.     {
  1673.         if (! ($class->isVersioned && $this->isLoaded($managedCopy) && $this->isLoaded($entity))) {
  1674.             return;
  1675.         }
  1676.         $reflField          $class->reflFields[$class->versionField];
  1677.         $managedCopyVersion $reflField->getValue($managedCopy);
  1678.         $entityVersion      $reflField->getValue($entity);
  1679.         // Throw exception if versions don't match.
  1680.         if ($managedCopyVersion == $entityVersion) {
  1681.             return;
  1682.         }
  1683.         throw OptimisticLockException::lockFailedVersionMismatch($entity$entityVersion$managedCopyVersion);
  1684.     }
  1685.     /**
  1686.      * Tests if an entity is loaded - must either be a loaded proxy or not a proxy
  1687.      *
  1688.      * @param object $entity
  1689.      *
  1690.      * @return bool
  1691.      */
  1692.     private function isLoaded($entity)
  1693.     {
  1694.         return !($entity instanceof Proxy) || $entity->__isInitialized();
  1695.     }
  1696.     /**
  1697.      * Sets/adds associated managed copies into the previous entity's association field
  1698.      *
  1699.      * @param object $entity
  1700.      * @param array  $association
  1701.      * @param object $previousManagedCopy
  1702.      * @param object $managedCopy
  1703.      *
  1704.      * @return void
  1705.      */
  1706.     private function updateAssociationWithMergedEntity($entity, array $association$previousManagedCopy$managedCopy)
  1707.     {
  1708.         $assocField $association['fieldName'];
  1709.         $prevClass  $this->em->getClassMetadata(get_class($previousManagedCopy));
  1710.         if ($association['type'] & ClassMetadata::TO_ONE) {
  1711.             $prevClass->reflFields[$assocField]->setValue($previousManagedCopy$managedCopy);
  1712.             return;
  1713.         }
  1714.         $value   $prevClass->reflFields[$assocField]->getValue($previousManagedCopy);
  1715.         $value[] = $managedCopy;
  1716.         if ($association['type'] == ClassMetadata::ONE_TO_MANY) {
  1717.             $class $this->em->getClassMetadata(get_class($entity));
  1718.             $class->reflFields[$association['mappedBy']]->setValue($managedCopy$previousManagedCopy);
  1719.         }
  1720.     }
  1721.     /**
  1722.      * Detaches an entity from the persistence management. It's persistence will
  1723.      * no longer be managed by Doctrine.
  1724.      *
  1725.      * @param object $entity The entity to detach.
  1726.      *
  1727.      * @return void
  1728.      *
  1729.      * @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
  1730.      */
  1731.     public function detach($entity)
  1732.     {
  1733.         $visited = [];
  1734.         $this->doDetach($entity$visited);
  1735.     }
  1736.     /**
  1737.      * Executes a detach operation on the given entity.
  1738.      *
  1739.      * @param object  $entity
  1740.      * @param array   $visited
  1741.      * @param boolean $noCascade if true, don't cascade detach operation.
  1742.      *
  1743.      * @return void
  1744.      */
  1745.     private function doDetach($entity, array &$visited$noCascade false)
  1746.     {
  1747.         $oid spl_object_hash($entity);
  1748.         if (isset($visited[$oid])) {
  1749.             return; // Prevent infinite recursion
  1750.         }
  1751.         $visited[$oid] = $entity// mark visited
  1752.         switch ($this->getEntityState($entityself::STATE_DETACHED)) {
  1753.             case self::STATE_MANAGED:
  1754.                 if ($this->isInIdentityMap($entity)) {
  1755.                     $this->removeFromIdentityMap($entity);
  1756.                 }
  1757.                 unset(
  1758.                     $this->entityInsertions[$oid],
  1759.                     $this->entityUpdates[$oid],
  1760.                     $this->entityDeletions[$oid],
  1761.                     $this->entityIdentifiers[$oid],
  1762.                     $this->entityStates[$oid],
  1763.                     $this->originalEntityData[$oid]
  1764.                 );
  1765.                 break;
  1766.             case self::STATE_NEW:
  1767.             case self::STATE_DETACHED:
  1768.                 return;
  1769.         }
  1770.         if ( ! $noCascade) {
  1771.             $this->cascadeDetach($entity$visited);
  1772.         }
  1773.     }
  1774.     /**
  1775.      * Refreshes the state of the given entity from the database, overwriting
  1776.      * any local, unpersisted changes.
  1777.      *
  1778.      * @param object $entity The entity to refresh.
  1779.      *
  1780.      * @return void
  1781.      *
  1782.      * @throws InvalidArgumentException If the entity is not MANAGED.
  1783.      */
  1784.     public function refresh($entity)
  1785.     {
  1786.         $visited = [];
  1787.         $this->doRefresh($entity$visited);
  1788.     }
  1789.     /**
  1790.      * Executes a refresh operation on an entity.
  1791.      *
  1792.      * @param object $entity  The entity to refresh.
  1793.      * @param array  $visited The already visited entities during cascades.
  1794.      *
  1795.      * @return void
  1796.      *
  1797.      * @throws ORMInvalidArgumentException If the entity is not MANAGED.
  1798.      */
  1799.     private function doRefresh($entity, array &$visited)
  1800.     {
  1801.         $oid spl_object_hash($entity);
  1802.         if (isset($visited[$oid])) {
  1803.             return; // Prevent infinite recursion
  1804.         }
  1805.         $visited[$oid] = $entity// mark visited
  1806.         $class $this->em->getClassMetadata(get_class($entity));
  1807.         if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
  1808.             throw ORMInvalidArgumentException::entityNotManaged($entity);
  1809.         }
  1810.         $this->getEntityPersister($class->name)->refresh(
  1811.             array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
  1812.             $entity
  1813.         );
  1814.         $this->cascadeRefresh($entity$visited);
  1815.     }
  1816.     /**
  1817.      * Cascades a refresh operation to associated entities.
  1818.      *
  1819.      * @param object $entity
  1820.      * @param array  $visited
  1821.      *
  1822.      * @return void
  1823.      */
  1824.     private function cascadeRefresh($entity, array &$visited)
  1825.     {
  1826.         $class $this->em->getClassMetadata(get_class($entity));
  1827.         $associationMappings array_filter(
  1828.             $class->associationMappings,
  1829.             function ($assoc) { return $assoc['isCascadeRefresh']; }
  1830.         );
  1831.         foreach ($associationMappings as $assoc) {
  1832.             $relatedEntities $class->reflFields[$assoc['fieldName']]->getValue($entity);
  1833.             switch (true) {
  1834.                 case ($relatedEntities instanceof PersistentCollection):
  1835.                     // Unwrap so that foreach() does not initialize
  1836.                     $relatedEntities $relatedEntities->unwrap();
  1837.                     // break; is commented intentionally!
  1838.                 case ($relatedEntities instanceof Collection):
  1839.                 case (is_array($relatedEntities)):
  1840.                     foreach ($relatedEntities as $relatedEntity) {
  1841.                         $this->doRefresh($relatedEntity$visited);
  1842.                     }
  1843.                     break;
  1844.                 case ($relatedEntities !== null):
  1845.                     $this->doRefresh($relatedEntities$visited);
  1846.                     break;
  1847.                 default:
  1848.                     // Do nothing
  1849.             }
  1850.         }
  1851.     }
  1852.     /**
  1853.      * Cascades a detach operation to associated entities.
  1854.      *
  1855.      * @param object $entity
  1856.      * @param array  $visited
  1857.      *
  1858.      * @return void
  1859.      */
  1860.     private function cascadeDetach($entity, array &$visited)
  1861.     {
  1862.         $class $this->em->getClassMetadata(get_class($entity));
  1863.         $associationMappings array_filter(
  1864.             $class->associationMappings,
  1865.             function ($assoc) { return $assoc['isCascadeDetach']; }
  1866.         );
  1867.         foreach ($associationMappings as $assoc) {
  1868.             $relatedEntities $class->reflFields[$assoc['fieldName']]->getValue($entity);
  1869.             switch (true) {
  1870.                 case ($relatedEntities instanceof PersistentCollection):
  1871.                     // Unwrap so that foreach() does not initialize
  1872.                     $relatedEntities $relatedEntities->unwrap();
  1873.                     // break; is commented intentionally!
  1874.                 case ($relatedEntities instanceof Collection):
  1875.                 case (is_array($relatedEntities)):
  1876.                     foreach ($relatedEntities as $relatedEntity) {
  1877.                         $this->doDetach($relatedEntity$visited);
  1878.                     }
  1879.                     break;
  1880.                 case ($relatedEntities !== null):
  1881.                     $this->doDetach($relatedEntities$visited);
  1882.                     break;
  1883.                 default:
  1884.                     // Do nothing
  1885.             }
  1886.         }
  1887.     }
  1888.     /**
  1889.      * Cascades a merge operation to associated entities.
  1890.      *
  1891.      * @param object $entity
  1892.      * @param object $managedCopy
  1893.      * @param array  $visited
  1894.      *
  1895.      * @return void
  1896.      */
  1897.     private function cascadeMerge($entity$managedCopy, array &$visited)
  1898.     {
  1899.         $class $this->em->getClassMetadata(get_class($entity));
  1900.         $associationMappings array_filter(
  1901.             $class->associationMappings,
  1902.             function ($assoc) { return $assoc['isCascadeMerge']; }
  1903.         );
  1904.         foreach ($associationMappings as $assoc) {
  1905.             $relatedEntities $class->reflFields[$assoc['fieldName']]->getValue($entity);
  1906.             if ($relatedEntities instanceof Collection) {
  1907.                 if ($relatedEntities === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) {
  1908.                     continue;
  1909.                 }
  1910.                 if ($relatedEntities instanceof PersistentCollection) {
  1911.                     // Unwrap so that foreach() does not initialize
  1912.                     $relatedEntities $relatedEntities->unwrap();
  1913.                 }
  1914.                 foreach ($relatedEntities as $relatedEntity) {
  1915.                     $this->doMerge($relatedEntity$visited$managedCopy$assoc);
  1916.                 }
  1917.             } else if ($relatedEntities !== null) {
  1918.                 $this->doMerge($relatedEntities$visited$managedCopy$assoc);
  1919.             }
  1920.         }
  1921.     }
  1922.     /**
  1923.      * Cascades the save operation to associated entities.
  1924.      *
  1925.      * @param object $entity
  1926.      * @param array  $visited
  1927.      *
  1928.      * @return void
  1929.      */
  1930.     private function cascadePersist($entity, array &$visited)
  1931.     {
  1932.         $class $this->em->getClassMetadata(get_class($entity));
  1933.         $associationMappings array_filter(
  1934.             $class->associationMappings,
  1935.             function ($assoc) { return $assoc['isCascadePersist']; }
  1936.         );
  1937.         foreach ($associationMappings as $assoc) {
  1938.             $relatedEntities $class->reflFields[$assoc['fieldName']]->getValue($entity);
  1939.             switch (true) {
  1940.                 case ($relatedEntities instanceof PersistentCollection):
  1941.                     // Unwrap so that foreach() does not initialize
  1942.                     $relatedEntities $relatedEntities->unwrap();
  1943.                     // break; is commented intentionally!
  1944.                 case ($relatedEntities instanceof Collection):
  1945.                 case (is_array($relatedEntities)):
  1946.                     if (($assoc['type'] & ClassMetadata::TO_MANY) <= 0) {
  1947.                         throw ORMInvalidArgumentException::invalidAssociation(
  1948.                             $this->em->getClassMetadata($assoc['targetEntity']),
  1949.                             $assoc,
  1950.                             $relatedEntities
  1951.                         );
  1952.                     }
  1953.                     foreach ($relatedEntities as $relatedEntity) {
  1954.                         $this->doPersist($relatedEntity$visited);
  1955.                     }
  1956.                     break;
  1957.                 case ($relatedEntities !== null):
  1958.                     if (! $relatedEntities instanceof $assoc['targetEntity']) {
  1959.                         throw ORMInvalidArgumentException::invalidAssociation(
  1960.                             $this->em->getClassMetadata($assoc['targetEntity']),
  1961.                             $assoc,
  1962.                             $relatedEntities
  1963.                         );
  1964.                     }
  1965.                     $this->doPersist($relatedEntities$visited);
  1966.                     break;
  1967.                 default:
  1968.                     // Do nothing
  1969.             }
  1970.         }
  1971.     }
  1972.     /**
  1973.      * Cascades the delete operation to associated entities.
  1974.      *
  1975.      * @param object $entity
  1976.      * @param array  $visited
  1977.      *
  1978.      * @return void
  1979.      */
  1980.     private function cascadeRemove($entity, array &$visited)
  1981.     {
  1982.         $class $this->em->getClassMetadata(get_class($entity));
  1983.         $associationMappings array_filter(
  1984.             $class->associationMappings,
  1985.             function ($assoc) { return $assoc['isCascadeRemove']; }
  1986.         );
  1987.         $entitiesToCascade = [];
  1988.         foreach ($associationMappings as $assoc) {
  1989.             if ($entity instanceof Proxy && !$entity->__isInitialized__) {
  1990.                 $entity->__load();
  1991.             }
  1992.             $relatedEntities $class->reflFields[$assoc['fieldName']]->getValue($entity);
  1993.             switch (true) {
  1994.                 case ($relatedEntities instanceof Collection):
  1995.                 case (is_array($relatedEntities)):
  1996.                     // If its a PersistentCollection initialization is intended! No unwrap!
  1997.                     foreach ($relatedEntities as $relatedEntity) {
  1998.                         $entitiesToCascade[] = $relatedEntity;
  1999.                     }
  2000.                     break;
  2001.                 case ($relatedEntities !== null):
  2002.                     $entitiesToCascade[] = $relatedEntities;
  2003.                     break;
  2004.                 default:
  2005.                     // Do nothing
  2006.             }
  2007.         }
  2008.         foreach ($entitiesToCascade as $relatedEntity) {
  2009.             $this->doRemove($relatedEntity$visited);
  2010.         }
  2011.     }
  2012.     /**
  2013.      * Acquire a lock on the given entity.
  2014.      *
  2015.      * @param object $entity
  2016.      * @param int    $lockMode
  2017.      * @param int    $lockVersion
  2018.      *
  2019.      * @return void
  2020.      *
  2021.      * @throws ORMInvalidArgumentException
  2022.      * @throws TransactionRequiredException
  2023.      * @throws OptimisticLockException
  2024.      */
  2025.     public function lock($entity$lockMode$lockVersion null)
  2026.     {
  2027.         if ($entity === null) {
  2028.             throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock().");
  2029.         }
  2030.         if ($this->getEntityState($entityself::STATE_DETACHED) != self::STATE_MANAGED) {
  2031.             throw ORMInvalidArgumentException::entityNotManaged($entity);
  2032.         }
  2033.         $class $this->em->getClassMetadata(get_class($entity));
  2034.         switch (true) {
  2035.             case LockMode::OPTIMISTIC === $lockMode:
  2036.                 if ( ! $class->isVersioned) {
  2037.                     throw OptimisticLockException::notVersioned($class->name);
  2038.                 }
  2039.                 if ($lockVersion === null) {
  2040.                     return;
  2041.                 }
  2042.                 if ($entity instanceof Proxy && !$entity->__isInitialized__) {
  2043.                     $entity->__load();
  2044.                 }
  2045.                 $entityVersion $class->reflFields[$class->versionField]->getValue($entity);
  2046.                 if ($entityVersion != $lockVersion) {
  2047.                     throw OptimisticLockException::lockFailedVersionMismatch($entity$lockVersion$entityVersion);
  2048.                 }
  2049.                 break;
  2050.             case LockMode::NONE === $lockMode:
  2051.             case LockMode::PESSIMISTIC_READ === $lockMode:
  2052.             case LockMode::PESSIMISTIC_WRITE === $lockMode:
  2053.                 if (!$this->em->getConnection()->isTransactionActive()) {
  2054.                     throw TransactionRequiredException::transactionRequired();
  2055.                 }
  2056.                 $oid spl_object_hash($entity);
  2057.                 $this->getEntityPersister($class->name)->lock(
  2058.                     array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
  2059.                     $lockMode
  2060.                 );
  2061.                 break;
  2062.             default:
  2063.                 // Do nothing
  2064.         }
  2065.     }
  2066.     /**
  2067.      * Gets the CommitOrderCalculator used by the UnitOfWork to order commits.
  2068.      *
  2069.      * @return \Doctrine\ORM\Internal\CommitOrderCalculator
  2070.      */
  2071.     public function getCommitOrderCalculator()
  2072.     {
  2073.         return new Internal\CommitOrderCalculator();
  2074.     }
  2075.     /**
  2076.      * Clears the UnitOfWork.
  2077.      *
  2078.      * @param string|null $entityName if given, only entities of this type will get detached.
  2079.      *
  2080.      * @return void
  2081.      *
  2082.      * @throws ORMInvalidArgumentException if an invalid entity name is given
  2083.      */
  2084.     public function clear($entityName null)
  2085.     {
  2086.         if ($entityName === null) {
  2087.             $this->identityMap                    =
  2088.             $this->entityIdentifiers              =
  2089.             $this->originalEntityData             =
  2090.             $this->entityChangeSets               =
  2091.             $this->entityStates                   =
  2092.             $this->scheduledForSynchronization    =
  2093.             $this->entityInsertions               =
  2094.             $this->entityUpdates                  =
  2095.             $this->entityDeletions                =
  2096.             $this->nonCascadedNewDetectedEntities =
  2097.             $this->collectionDeletions            =
  2098.             $this->collectionUpdates              =
  2099.             $this->extraUpdates                   =
  2100.             $this->readOnlyObjects                =
  2101.             $this->visitedCollections             =
  2102.             $this->eagerLoadingEntities           =
  2103.             $this->orphanRemovals                 = [];
  2104.         } else {
  2105.             $this->clearIdentityMapForEntityName($entityName);
  2106.             $this->clearEntityInsertionsForEntityName($entityName);
  2107.         }
  2108.         if ($this->evm->hasListeners(Events::onClear)) {
  2109.             $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em$entityName));
  2110.         }
  2111.     }
  2112.     /**
  2113.      * INTERNAL:
  2114.      * Schedules an orphaned entity for removal. The remove() operation will be
  2115.      * invoked on that entity at the beginning of the next commit of this
  2116.      * UnitOfWork.
  2117.      *
  2118.      * @ignore
  2119.      *
  2120.      * @param object $entity
  2121.      *
  2122.      * @return void
  2123.      */
  2124.     public function scheduleOrphanRemoval($entity)
  2125.     {
  2126.         $this->orphanRemovals[spl_object_hash($entity)] = $entity;
  2127.     }
  2128.     /**
  2129.      * INTERNAL:
  2130.      * Cancels a previously scheduled orphan removal.
  2131.      *
  2132.      * @ignore
  2133.      *
  2134.      * @param object $entity
  2135.      *
  2136.      * @return void
  2137.      */
  2138.     public function cancelOrphanRemoval($entity)
  2139.     {
  2140.         unset($this->orphanRemovals[spl_object_hash($entity)]);
  2141.     }
  2142.     /**
  2143.      * INTERNAL:
  2144.      * Schedules a complete collection for removal when this UnitOfWork commits.
  2145.      *
  2146.      * @param PersistentCollection $coll
  2147.      *
  2148.      * @return void
  2149.      */
  2150.     public function scheduleCollectionDeletion(PersistentCollection $coll)
  2151.     {
  2152.         $coid spl_object_hash($coll);
  2153.         // TODO: if $coll is already scheduled for recreation ... what to do?
  2154.         // Just remove $coll from the scheduled recreations?
  2155.         unset($this->collectionUpdates[$coid]);
  2156.         $this->collectionDeletions[$coid] = $coll;
  2157.     }
  2158.     /**
  2159.      * @param PersistentCollection $coll
  2160.      *
  2161.      * @return bool
  2162.      */
  2163.     public function isCollectionScheduledForDeletion(PersistentCollection $coll)
  2164.     {
  2165.         return isset($this->collectionDeletions[spl_object_hash($coll)]);
  2166.     }
  2167.     /**
  2168.      * @param ClassMetadata $class
  2169.      *
  2170.      * @return ObjectManagerAware|object
  2171.      */
  2172.     private function newInstance($class)
  2173.     {
  2174.         $entity $class->newInstance();
  2175.         if ($entity instanceof ObjectManagerAware) {
  2176.             $entity->injectObjectManager($this->em$class);
  2177.         }
  2178.         return $entity;
  2179.     }
  2180.     /**
  2181.      * INTERNAL:
  2182.      * Creates an entity. Used for reconstitution of persistent entities.
  2183.      *
  2184.      * Internal note: Highly performance-sensitive method.
  2185.      *
  2186.      * @ignore
  2187.      *
  2188.      * @param string $className The name of the entity class.
  2189.      * @param array  $data      The data for the entity.
  2190.      * @param array  $hints     Any hints to account for during reconstitution/lookup of the entity.
  2191.      *
  2192.      * @return object The managed entity instance.
  2193.      *
  2194.      * @todo Rename: getOrCreateEntity
  2195.      */
  2196.     public function createEntity($className, array $data, &$hints = [])
  2197.     {
  2198.         $class $this->em->getClassMetadata($className);
  2199.         $id $this->identifierFlattener->flattenIdentifier($class$data);
  2200.         $idHash implode(' '$id);
  2201.         if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
  2202.             $entity $this->identityMap[$class->rootEntityName][$idHash];
  2203.             $oid spl_object_hash($entity);
  2204.             if (
  2205.                 isset($hints[Query::HINT_REFRESH])
  2206.                 && isset($hints[Query::HINT_REFRESH_ENTITY])
  2207.                 && ($unmanagedProxy $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
  2208.                 && $unmanagedProxy instanceof Proxy
  2209.                 && $this->isIdentifierEquals($unmanagedProxy$entity)
  2210.             ) {
  2211.                 // DDC-1238 - we have a managed instance, but it isn't the provided one.
  2212.                 // Therefore we clear its identifier. Also, we must re-fetch metadata since the
  2213.                 // refreshed object may be anything
  2214.                 foreach ($class->identifier as $fieldName) {
  2215.                     $class->reflFields[$fieldName]->setValue($unmanagedProxynull);
  2216.                 }
  2217.                 return $unmanagedProxy;
  2218.             }
  2219.             if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
  2220.                 $entity->__setInitialized(true);
  2221.                 if ($entity instanceof NotifyPropertyChanged) {
  2222.                     $entity->addPropertyChangedListener($this);
  2223.                 }
  2224.             } else {
  2225.                 if ( ! isset($hints[Query::HINT_REFRESH])
  2226.                     || (isset($hints[Query::HINT_REFRESH_ENTITY]) && $hints[Query::HINT_REFRESH_ENTITY] !== $entity)) {
  2227.                     return $entity;
  2228.                 }
  2229.             }
  2230.             // inject ObjectManager upon refresh.
  2231.             if ($entity instanceof ObjectManagerAware) {
  2232.                 $entity->injectObjectManager($this->em$class);
  2233.             }
  2234.             $this->originalEntityData[$oid] = $data;
  2235.         } else {
  2236.             $entity $this->newInstance($class);
  2237.             $oid    spl_object_hash($entity);
  2238.             $this->entityIdentifiers[$oid]  = $id;
  2239.             $this->entityStates[$oid]       = self::STATE_MANAGED;
  2240.             $this->originalEntityData[$oid] = $data;
  2241.             $this->identityMap[$class->rootEntityName][$idHash] = $entity;
  2242.             if ($entity instanceof NotifyPropertyChanged) {
  2243.                 $entity->addPropertyChangedListener($this);
  2244.             }
  2245.         }
  2246.         foreach ($data as $field => $value) {
  2247.             if (isset($class->fieldMappings[$field])) {
  2248.                 $class->reflFields[$field]->setValue($entity$value);
  2249.             }
  2250.         }
  2251.         // Loading the entity right here, if its in the eager loading map get rid of it there.
  2252.         unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]);
  2253.         if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) {
  2254.             unset($this->eagerLoadingEntities[$class->rootEntityName]);
  2255.         }
  2256.         // Properly initialize any unfetched associations, if partial objects are not allowed.
  2257.         if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
  2258.             return $entity;
  2259.         }
  2260.         foreach ($class->associationMappings as $field => $assoc) {
  2261.             // Check if the association is not among the fetch-joined associations already.
  2262.             if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
  2263.                 continue;
  2264.             }
  2265.             $targetClass $this->em->getClassMetadata($assoc['targetEntity']);
  2266.             switch (true) {
  2267.                 case ($assoc['type'] & ClassMetadata::TO_ONE):
  2268.                     if ( ! $assoc['isOwningSide']) {
  2269.                         // use the given entity association
  2270.                         if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
  2271.                             $this->originalEntityData[$oid][$field] = $data[$field];
  2272.                             $class->reflFields[$field]->setValue($entity$data[$field]);
  2273.                             $targetClass->reflFields[$assoc['mappedBy']]->setValue($data[$field], $entity);
  2274.                             continue 2;
  2275.                         }
  2276.                         // Inverse side of x-to-one can never be lazy
  2277.                         $class->reflFields[$field]->setValue($entity$this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc$entity));
  2278.                         continue 2;
  2279.                     }
  2280.                     // use the entity association
  2281.                     if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
  2282.                         $class->reflFields[$field]->setValue($entity$data[$field]);
  2283.                         $this->originalEntityData[$oid][$field] = $data[$field];
  2284.                         break;
  2285.                     }
  2286.                     $associatedId = [];
  2287.                     // TODO: Is this even computed right in all cases of composite keys?
  2288.                     foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
  2289.                         $joinColumnValue $data[$srcColumn] ?? null;
  2290.                         if ($joinColumnValue !== null) {
  2291.                             if ($targetClass->containsForeignIdentifier) {
  2292.                                 $associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
  2293.                             } else {
  2294.                                 $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
  2295.                             }
  2296.                         } elseif ($targetClass->containsForeignIdentifier
  2297.                             && in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifiertrue)
  2298.                         ) {
  2299.                             // the missing key is part of target's entity primary key
  2300.                             $associatedId = [];
  2301.                             break;
  2302.                         }
  2303.                     }
  2304.                     if ( ! $associatedId) {
  2305.                         // Foreign key is NULL
  2306.                         $class->reflFields[$field]->setValue($entitynull);
  2307.                         $this->originalEntityData[$oid][$field] = null;
  2308.                         break;
  2309.                     }
  2310.                     if ( ! isset($hints['fetchMode'][$class->name][$field])) {
  2311.                         $hints['fetchMode'][$class->name][$field] = $assoc['fetch'];
  2312.                     }
  2313.                     // Foreign key is set
  2314.                     // Check identity map first
  2315.                     // FIXME: Can break easily with composite keys if join column values are in
  2316.                     //        wrong order. The correct order is the one in ClassMetadata#identifier.
  2317.                     $relatedIdHash implode(' '$associatedId);
  2318.                     switch (true) {
  2319.                         case (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])):
  2320.                             $newValue $this->identityMap[$targetClass->rootEntityName][$relatedIdHash];
  2321.                             // If this is an uninitialized proxy, we are deferring eager loads,
  2322.                             // this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
  2323.                             // then we can append this entity for eager loading!
  2324.                             if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER &&
  2325.                                 isset($hints[self::HINT_DEFEREAGERLOAD]) &&
  2326.                                 !$targetClass->isIdentifierComposite &&
  2327.                                 $newValue instanceof Proxy &&
  2328.                                 $newValue->__isInitialized__ === false) {
  2329.                                 $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
  2330.                             }
  2331.                             break;
  2332.                         case ($targetClass->subClasses):
  2333.                             // If it might be a subtype, it can not be lazy. There isn't even
  2334.                             // a way to solve this with deferred eager loading, which means putting
  2335.                             // an entity with subclasses at a *-to-one location is really bad! (performance-wise)
  2336.                             $newValue $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc$entity$associatedId);
  2337.                             break;
  2338.                         default:
  2339.                             switch (true) {
  2340.                                 // We are negating the condition here. Other cases will assume it is valid!
  2341.                                 case ($hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER):
  2342.                                     $newValue $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
  2343.                                     break;
  2344.                                 // Deferred eager load only works for single identifier classes
  2345.                                 case (isset($hints[self::HINT_DEFEREAGERLOAD]) && ! $targetClass->isIdentifierComposite):
  2346.                                     // TODO: Is there a faster approach?
  2347.                                     $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
  2348.                                     $newValue $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
  2349.                                     break;
  2350.                                 default:
  2351.                                     // TODO: This is very imperformant, ignore it?
  2352.                                     $newValue $this->em->find($assoc['targetEntity'], $associatedId);
  2353.                                     break;
  2354.                             }
  2355.                             // PERF: Inlined & optimized code from UnitOfWork#registerManaged()
  2356.                             $newValueOid spl_object_hash($newValue);
  2357.                             $this->entityIdentifiers[$newValueOid] = $associatedId;
  2358.                             $this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
  2359.                             if (
  2360.                                 $newValue instanceof NotifyPropertyChanged &&
  2361.                                 ( ! $newValue instanceof Proxy || $newValue->__isInitialized())
  2362.                             ) {
  2363.                                 $newValue->addPropertyChangedListener($this);
  2364.                             }
  2365.                             $this->entityStates[$newValueOid] = self::STATE_MANAGED;
  2366.                             // make sure that when an proxy is then finally loaded, $this->originalEntityData is set also!
  2367.                             break;
  2368.                     }
  2369.                     $this->originalEntityData[$oid][$field] = $newValue;
  2370.                     $class->reflFields[$field]->setValue($entity$newValue);
  2371.                     if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
  2372.                         $inverseAssoc $targetClass->associationMappings[$assoc['inversedBy']];
  2373.                         $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue$entity);
  2374.                     }
  2375.                     break;
  2376.                 default:
  2377.                     // Ignore if its a cached collection
  2378.                     if (isset($hints[Query::HINT_CACHE_ENABLED]) && $class->getFieldValue($entity$field) instanceof PersistentCollection) {
  2379.                         break;
  2380.                     }
  2381.                     // use the given collection
  2382.                     if (isset($data[$field]) && $data[$field] instanceof PersistentCollection) {
  2383.                         $data[$field]->setOwner($entity$assoc);
  2384.                         $class->reflFields[$field]->setValue($entity$data[$field]);
  2385.                         $this->originalEntityData[$oid][$field] = $data[$field];
  2386.                         break;
  2387.                     }
  2388.                     // Inject collection
  2389.                     $pColl = new PersistentCollection($this->em$targetClass, new ArrayCollection);
  2390.                     $pColl->setOwner($entity$assoc);
  2391.                     $pColl->setInitialized(false);
  2392.                     $reflField $class->reflFields[$field];
  2393.                     $reflField->setValue($entity$pColl);
  2394.                     if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
  2395.                         $this->loadCollection($pColl);
  2396.                         $pColl->takeSnapshot();
  2397.                     }
  2398.                     $this->originalEntityData[$oid][$field] = $pColl;
  2399.                     break;
  2400.             }
  2401.         }
  2402.         // defer invoking of postLoad event to hydration complete step
  2403.         $this->hydrationCompleteHandler->deferPostLoadInvoking($class$entity);
  2404.         return $entity;
  2405.     }
  2406.     /**
  2407.      * @return void
  2408.      */
  2409.     public function triggerEagerLoads()
  2410.     {
  2411.         if ( ! $this->eagerLoadingEntities) {
  2412.             return;
  2413.         }
  2414.         // avoid infinite recursion
  2415.         $eagerLoadingEntities       $this->eagerLoadingEntities;
  2416.         $this->eagerLoadingEntities = [];
  2417.         foreach ($eagerLoadingEntities as $entityName => $ids) {
  2418.             if ( ! $ids) {
  2419.                 continue;
  2420.             }
  2421.             $class $this->em->getClassMetadata($entityName);
  2422.             $this->getEntityPersister($entityName)->loadAll(
  2423.                 array_combine($class->identifier, [array_values($ids)])
  2424.             );
  2425.         }
  2426.     }
  2427.     /**
  2428.      * Initializes (loads) an uninitialized persistent collection of an entity.
  2429.      *
  2430.      * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.
  2431.      *
  2432.      * @return void
  2433.      *
  2434.      * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
  2435.      */
  2436.     public function loadCollection(PersistentCollection $collection)
  2437.     {
  2438.         $assoc     $collection->getMapping();
  2439.         $persister $this->getEntityPersister($assoc['targetEntity']);
  2440.         switch ($assoc['type']) {
  2441.             case ClassMetadata::ONE_TO_MANY:
  2442.                 $persister->loadOneToManyCollection($assoc$collection->getOwner(), $collection);
  2443.                 break;
  2444.             case ClassMetadata::MANY_TO_MANY:
  2445.                 $persister->loadManyToManyCollection($assoc$collection->getOwner(), $collection);
  2446.                 break;
  2447.         }
  2448.         $collection->setInitialized(true);
  2449.     }
  2450.     /**
  2451.      * Gets the identity map of the UnitOfWork.
  2452.      *
  2453.      * @return array
  2454.      */
  2455.     public function getIdentityMap()
  2456.     {
  2457.         return $this->identityMap;
  2458.     }
  2459.     /**
  2460.      * Gets the original data of an entity. The original data is the data that was
  2461.      * present at the time the entity was reconstituted from the database.
  2462.      *
  2463.      * @param object $entity
  2464.      *
  2465.      * @return array
  2466.      */
  2467.     public function getOriginalEntityData($entity)
  2468.     {
  2469.         $oid spl_object_hash($entity);
  2470.         return isset($this->originalEntityData[$oid])
  2471.             ? $this->originalEntityData[$oid]
  2472.             : [];
  2473.     }
  2474.     /**
  2475.      * @ignore
  2476.      *
  2477.      * @param object $entity
  2478.      * @param array  $data
  2479.      *
  2480.      * @return void
  2481.      */
  2482.     public function setOriginalEntityData($entity, array $data)
  2483.     {
  2484.         $this->originalEntityData[spl_object_hash($entity)] = $data;
  2485.     }
  2486.     /**
  2487.      * INTERNAL:
  2488.      * Sets a property value of the original data array of an entity.
  2489.      *
  2490.      * @ignore
  2491.      *
  2492.      * @param string $oid
  2493.      * @param string $property
  2494.      * @param mixed  $value
  2495.      *
  2496.      * @return void
  2497.      */
  2498.     public function setOriginalEntityProperty($oid$property$value)
  2499.     {
  2500.         $this->originalEntityData[$oid][$property] = $value;
  2501.     }
  2502.     /**
  2503.      * Gets the identifier of an entity.
  2504.      * The returned value is always an array of identifier values. If the entity
  2505.      * has a composite identifier then the identifier values are in the same
  2506.      * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
  2507.      *
  2508.      * @param object $entity
  2509.      *
  2510.      * @return array The identifier values.
  2511.      */
  2512.     public function getEntityIdentifier($entity)
  2513.     {
  2514.         return $this->entityIdentifiers[spl_object_hash($entity)];
  2515.     }
  2516.     /**
  2517.      * Processes an entity instance to extract their identifier values.
  2518.      *
  2519.      * @param object $entity The entity instance.
  2520.      *
  2521.      * @return mixed A scalar value.
  2522.      *
  2523.      * @throws \Doctrine\ORM\ORMInvalidArgumentException
  2524.      */
  2525.     public function getSingleIdentifierValue($entity)
  2526.     {
  2527.         $class $this->em->getClassMetadata(get_class($entity));
  2528.         if ($class->isIdentifierComposite) {
  2529.             throw ORMInvalidArgumentException::invalidCompositeIdentifier();
  2530.         }
  2531.         $values $this->isInIdentityMap($entity)
  2532.             ? $this->getEntityIdentifier($entity)
  2533.             : $class->getIdentifierValues($entity);
  2534.         return isset($values[$class->identifier[0]]) ? $values[$class->identifier[0]] : null;
  2535.     }
  2536.     /**
  2537.      * Tries to find an entity with the given identifier in the identity map of
  2538.      * this UnitOfWork.
  2539.      *
  2540.      * @param mixed  $id            The entity identifier to look for.
  2541.      * @param string $rootClassName The name of the root class of the mapped entity hierarchy.
  2542.      *
  2543.      * @return object|false Returns the entity with the specified identifier if it exists in
  2544.      *                      this UnitOfWork, FALSE otherwise.
  2545.      */
  2546.     public function tryGetById($id$rootClassName)
  2547.     {
  2548.         $idHash implode(' ', (array) $id);
  2549.         return isset($this->identityMap[$rootClassName][$idHash])
  2550.             ? $this->identityMap[$rootClassName][$idHash]
  2551.             : false;
  2552.     }
  2553.     /**
  2554.      * Schedules an entity for dirty-checking at commit-time.
  2555.      *
  2556.      * @param object $entity The entity to schedule for dirty-checking.
  2557.      *
  2558.      * @return void
  2559.      *
  2560.      * @todo Rename: scheduleForSynchronization
  2561.      */
  2562.     public function scheduleForDirtyCheck($entity)
  2563.     {
  2564.         $rootClassName $this->em->getClassMetadata(get_class($entity))->rootEntityName;
  2565.         $this->scheduledForSynchronization[$rootClassName][spl_object_hash($entity)] = $entity;
  2566.     }
  2567.     /**
  2568.      * Checks whether the UnitOfWork has any pending insertions.
  2569.      *
  2570.      * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
  2571.      */
  2572.     public function hasPendingInsertions()
  2573.     {
  2574.         return ! empty($this->entityInsertions);
  2575.     }
  2576.     /**
  2577.      * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
  2578.      * number of entities in the identity map.
  2579.      *
  2580.      * @return integer
  2581.      */
  2582.     public function size()
  2583.     {
  2584.         $countArray array_map('count'$this->identityMap);
  2585.         return array_sum($countArray);
  2586.     }
  2587.     /**
  2588.      * Gets the EntityPersister for an Entity.
  2589.      *
  2590.      * @param string $entityName The name of the Entity.
  2591.      *
  2592.      * @return \Doctrine\ORM\Persisters\Entity\EntityPersister
  2593.      */
  2594.     public function getEntityPersister($entityName)
  2595.     {
  2596.         if (isset($this->persisters[$entityName])) {
  2597.             return $this->persisters[$entityName];
  2598.         }
  2599.         $class $this->em->getClassMetadata($entityName);
  2600.         switch (true) {
  2601.             case ($class->isInheritanceTypeNone()):
  2602.                 $persister = new BasicEntityPersister($this->em$class);
  2603.                 break;
  2604.             case ($class->isInheritanceTypeSingleTable()):
  2605.                 $persister = new SingleTablePersister($this->em$class);
  2606.                 break;
  2607.             case ($class->isInheritanceTypeJoined()):
  2608.                 $persister = new JoinedSubclassPersister($this->em$class);
  2609.                 break;
  2610.             default:
  2611.                 throw new \RuntimeException('No persister found for entity.');
  2612.         }
  2613.         if ($this->hasCache && $class->cache !== null) {
  2614.             $persister $this->em->getConfiguration()
  2615.                 ->getSecondLevelCacheConfiguration()
  2616.                 ->getCacheFactory()
  2617.                 ->buildCachedEntityPersister($this->em$persister$class);
  2618.         }
  2619.         $this->persisters[$entityName] = $persister;
  2620.         return $this->persisters[$entityName];
  2621.     }
  2622.     /**
  2623.      * Gets a collection persister for a collection-valued association.
  2624.      *
  2625.      * @param array $association
  2626.      *
  2627.      * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister
  2628.      */
  2629.     public function getCollectionPersister(array $association)
  2630.     {
  2631.         $role = isset($association['cache'])
  2632.             ? $association['sourceEntity'] . '::' $association['fieldName']
  2633.             : $association['type'];
  2634.         if (isset($this->collectionPersisters[$role])) {
  2635.             return $this->collectionPersisters[$role];
  2636.         }
  2637.         $persister ClassMetadata::ONE_TO_MANY === $association['type']
  2638.             ? new OneToManyPersister($this->em)
  2639.             : new ManyToManyPersister($this->em);
  2640.         if ($this->hasCache && isset($association['cache'])) {
  2641.             $persister $this->em->getConfiguration()
  2642.                 ->getSecondLevelCacheConfiguration()
  2643.                 ->getCacheFactory()
  2644.                 ->buildCachedCollectionPersister($this->em$persister$association);
  2645.         }
  2646.         $this->collectionPersisters[$role] = $persister;
  2647.         return $this->collectionPersisters[$role];
  2648.     }
  2649.     /**
  2650.      * INTERNAL:
  2651.      * Registers an entity as managed.
  2652.      *
  2653.      * @param object $entity The entity.
  2654.      * @param array  $id     The identifier values.
  2655.      * @param array  $data   The original entity data.
  2656.      *
  2657.      * @return void
  2658.      */
  2659.     public function registerManaged($entity, array $id, array $data)
  2660.     {
  2661.         $oid spl_object_hash($entity);
  2662.         $this->entityIdentifiers[$oid]  = $id;
  2663.         $this->entityStates[$oid]       = self::STATE_MANAGED;
  2664.         $this->originalEntityData[$oid] = $data;
  2665.         $this->addToIdentityMap($entity);
  2666.         if ($entity instanceof NotifyPropertyChanged && ( ! $entity instanceof Proxy || $entity->__isInitialized())) {
  2667.             $entity->addPropertyChangedListener($this);
  2668.         }
  2669.     }
  2670.     /**
  2671.      * INTERNAL:
  2672.      * Clears the property changeset of the entity with the given OID.
  2673.      *
  2674.      * @param string $oid The entity's OID.
  2675.      *
  2676.      * @return void
  2677.      */
  2678.     public function clearEntityChangeSet($oid)
  2679.     {
  2680.         unset($this->entityChangeSets[$oid]);
  2681.     }
  2682.     /* PropertyChangedListener implementation */
  2683.     /**
  2684.      * Notifies this UnitOfWork of a property change in an entity.
  2685.      *
  2686.      * @param object $sender       The entity that owns the property.
  2687.      * @param string $propertyName The name of the property that changed.
  2688.      * @param mixed  $oldValue     The old value of the property.
  2689.      * @param mixed  $newValue     The new value of the property.
  2690.      *
  2691.      * @return void
  2692.      */
  2693.     public function propertyChanged($sender$propertyName$oldValue$newValue)
  2694.     {
  2695.         $oid   spl_object_hash($sender);
  2696.         $class $this->em->getClassMetadata(get_class($sender));
  2697.         $isAssocField = isset($class->associationMappings[$propertyName]);
  2698.         if ( ! $isAssocField && ! isset($class->fieldMappings[$propertyName])) {
  2699.             return; // ignore non-persistent fields
  2700.         }
  2701.         // Update changeset and mark entity for synchronization
  2702.         $this->entityChangeSets[$oid][$propertyName] = [$oldValue$newValue];
  2703.         if ( ! isset($this->scheduledForSynchronization[$class->rootEntityName][$oid])) {
  2704.             $this->scheduleForDirtyCheck($sender);
  2705.         }
  2706.     }
  2707.     /**
  2708.      * Gets the currently scheduled entity insertions in this UnitOfWork.
  2709.      *
  2710.      * @return array
  2711.      */
  2712.     public function getScheduledEntityInsertions()
  2713.     {
  2714.         return $this->entityInsertions;
  2715.     }
  2716.     /**
  2717.      * Gets the currently scheduled entity updates in this UnitOfWork.
  2718.      *
  2719.      * @return array
  2720.      */
  2721.     public function getScheduledEntityUpdates()
  2722.     {
  2723.         return $this->entityUpdates;
  2724.     }
  2725.     /**
  2726.      * Gets the currently scheduled entity deletions in this UnitOfWork.
  2727.      *
  2728.      * @return array
  2729.      */
  2730.     public function getScheduledEntityDeletions()
  2731.     {
  2732.         return $this->entityDeletions;
  2733.     }
  2734.     /**
  2735.      * Gets the currently scheduled complete collection deletions
  2736.      *
  2737.      * @return array
  2738.      */
  2739.     public function getScheduledCollectionDeletions()
  2740.     {
  2741.         return $this->collectionDeletions;
  2742.     }
  2743.     /**
  2744.      * Gets the currently scheduled collection inserts, updates and deletes.
  2745.      *
  2746.      * @return array
  2747.      */
  2748.     public function getScheduledCollectionUpdates()
  2749.     {
  2750.         return $this->collectionUpdates;
  2751.     }
  2752.     /**
  2753.      * Helper method to initialize a lazy loading proxy or persistent collection.
  2754.      *
  2755.      * @param object $obj
  2756.      *
  2757.      * @return void
  2758.      */
  2759.     public function initializeObject($obj)
  2760.     {
  2761.         if ($obj instanceof Proxy) {
  2762.             $obj->__load();
  2763.             return;
  2764.         }
  2765.         if ($obj instanceof PersistentCollection) {
  2766.             $obj->initialize();
  2767.         }
  2768.     }
  2769.     /**
  2770.      * Helper method to show an object as string.
  2771.      *
  2772.      * @param object $obj
  2773.      *
  2774.      * @return string
  2775.      */
  2776.     private static function objToStr($obj)
  2777.     {
  2778.         return method_exists($obj'__toString') ? (string) $obj get_class($obj).'@'.spl_object_hash($obj);
  2779.     }
  2780.     /**
  2781.      * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
  2782.      *
  2783.      * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
  2784.      * on this object that might be necessary to perform a correct update.
  2785.      *
  2786.      * @param object $object
  2787.      *
  2788.      * @return void
  2789.      *
  2790.      * @throws ORMInvalidArgumentException
  2791.      */
  2792.     public function markReadOnly($object)
  2793.     {
  2794.         if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
  2795.             throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
  2796.         }
  2797.         $this->readOnlyObjects[spl_object_hash($object)] = true;
  2798.     }
  2799.     /**
  2800.      * Is this entity read only?
  2801.      *
  2802.      * @param object $object
  2803.      *
  2804.      * @return bool
  2805.      *
  2806.      * @throws ORMInvalidArgumentException
  2807.      */
  2808.     public function isReadOnly($object)
  2809.     {
  2810.         if ( ! is_object($object)) {
  2811.             throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
  2812.         }
  2813.         return isset($this->readOnlyObjects[spl_object_hash($object)]);
  2814.     }
  2815.     /**
  2816.      * Perform whatever processing is encapsulated here after completion of the transaction.
  2817.      */
  2818.     private function afterTransactionComplete()
  2819.     {
  2820.         $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
  2821.             $persister->afterTransactionComplete();
  2822.         });
  2823.     }
  2824.     /**
  2825.      * Perform whatever processing is encapsulated here after completion of the rolled-back.
  2826.      */
  2827.     private function afterTransactionRolledBack()
  2828.     {
  2829.         $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
  2830.             $persister->afterTransactionRolledBack();
  2831.         });
  2832.     }
  2833.     /**
  2834.      * Performs an action after the transaction.
  2835.      *
  2836.      * @param callable $callback
  2837.      */
  2838.     private function performCallbackOnCachedPersister(callable $callback)
  2839.     {
  2840.         if ( ! $this->hasCache) {
  2841.             return;
  2842.         }
  2843.         foreach (array_merge($this->persisters$this->collectionPersisters) as $persister) {
  2844.             if ($persister instanceof CachedPersister) {
  2845.                 $callback($persister);
  2846.             }
  2847.         }
  2848.     }
  2849.     private function dispatchOnFlushEvent()
  2850.     {
  2851.         if ($this->evm->hasListeners(Events::onFlush)) {
  2852.             $this->evm->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
  2853.         }
  2854.     }
  2855.     private function dispatchPostFlushEvent()
  2856.     {
  2857.         if ($this->evm->hasListeners(Events::postFlush)) {
  2858.             $this->evm->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
  2859.         }
  2860.     }
  2861.     /**
  2862.      * Verifies if two given entities actually are the same based on identifier comparison
  2863.      *
  2864.      * @param object $entity1
  2865.      * @param object $entity2
  2866.      *
  2867.      * @return bool
  2868.      */
  2869.     private function isIdentifierEquals($entity1$entity2)
  2870.     {
  2871.         if ($entity1 === $entity2) {
  2872.             return true;
  2873.         }
  2874.         $class $this->em->getClassMetadata(get_class($entity1));
  2875.         if ($class !== $this->em->getClassMetadata(get_class($entity2))) {
  2876.             return false;
  2877.         }
  2878.         $oid1 spl_object_hash($entity1);
  2879.         $oid2 spl_object_hash($entity2);
  2880.         $id1 = isset($this->entityIdentifiers[$oid1])
  2881.             ? $this->entityIdentifiers[$oid1]
  2882.             : $this->identifierFlattener->flattenIdentifier($class$class->getIdentifierValues($entity1));
  2883.         $id2 = isset($this->entityIdentifiers[$oid2])
  2884.             ? $this->entityIdentifiers[$oid2]
  2885.             : $this->identifierFlattener->flattenIdentifier($class$class->getIdentifierValues($entity2));
  2886.         return $id1 === $id2 || implode(' '$id1) === implode(' '$id2);
  2887.     }
  2888.     /**
  2889.      * @throws ORMInvalidArgumentException
  2890.      */
  2891.     private function assertThatThereAreNoUnintentionallyNonPersistedAssociations() : void
  2892.     {
  2893.         $entitiesNeedingCascadePersist = \array_diff_key($this->nonCascadedNewDetectedEntities$this->entityInsertions);
  2894.         $this->nonCascadedNewDetectedEntities = [];
  2895.         if ($entitiesNeedingCascadePersist) {
  2896.             throw ORMInvalidArgumentException::newEntitiesFoundThroughRelationships(
  2897.                 \array_values($entitiesNeedingCascadePersist)
  2898.             );
  2899.         }
  2900.     }
  2901.     /**
  2902.      * @param object $entity
  2903.      * @param object $managedCopy
  2904.      *
  2905.      * @throws ORMException
  2906.      * @throws OptimisticLockException
  2907.      * @throws TransactionRequiredException
  2908.      */
  2909.     private function mergeEntityStateIntoManagedCopy($entity$managedCopy)
  2910.     {
  2911.         if (! $this->isLoaded($entity)) {
  2912.             return;
  2913.         }
  2914.         if (! $this->isLoaded($managedCopy)) {
  2915.             $managedCopy->__load();
  2916.         }
  2917.         $class $this->em->getClassMetadata(get_class($entity));
  2918.         foreach ($this->reflectionPropertiesGetter->getProperties($class->name) as $prop) {
  2919.             $name $prop->name;
  2920.             $prop->setAccessible(true);
  2921.             if ( ! isset($class->associationMappings[$name])) {
  2922.                 if ( ! $class->isIdentifier($name)) {
  2923.                     $prop->setValue($managedCopy$prop->getValue($entity));
  2924.                 }
  2925.             } else {
  2926.                 $assoc2 $class->associationMappings[$name];
  2927.                 if ($assoc2['type'] & ClassMetadata::TO_ONE) {
  2928.                     $other $prop->getValue($entity);
  2929.                     if ($other === null) {
  2930.                         $prop->setValue($managedCopynull);
  2931.                     } else {
  2932.                         if ($other instanceof Proxy && !$other->__isInitialized()) {
  2933.                             // do not merge fields marked lazy that have not been fetched.
  2934.                             continue;
  2935.                         }
  2936.                         if ( ! $assoc2['isCascadeMerge']) {
  2937.                             if ($this->getEntityState($other) === self::STATE_DETACHED) {
  2938.                                 $targetClass $this->em->getClassMetadata($assoc2['targetEntity']);
  2939.                                 $relatedId   $targetClass->getIdentifierValues($other);
  2940.                                 if ($targetClass->subClasses) {
  2941.                                     $other $this->em->find($targetClass->name$relatedId);
  2942.                                 } else {
  2943.                                     $other $this->em->getProxyFactory()->getProxy(
  2944.                                         $assoc2['targetEntity'],
  2945.                                         $relatedId
  2946.                                     );
  2947.                                     $this->registerManaged($other$relatedId, []);
  2948.                                 }
  2949.                             }
  2950.                             $prop->setValue($managedCopy$other);
  2951.                         }
  2952.                     }
  2953.                 } else {
  2954.                     $mergeCol $prop->getValue($entity);
  2955.                     if ($mergeCol instanceof PersistentCollection && ! $mergeCol->isInitialized()) {
  2956.                         // do not merge fields marked lazy that have not been fetched.
  2957.                         // keep the lazy persistent collection of the managed copy.
  2958.                         continue;
  2959.                     }
  2960.                     $managedCol $prop->getValue($managedCopy);
  2961.                     if ( ! $managedCol) {
  2962.                         $managedCol = new PersistentCollection(
  2963.                             $this->em,
  2964.                             $this->em->getClassMetadata($assoc2['targetEntity']),
  2965.                             new ArrayCollection
  2966.                         );
  2967.                         $managedCol->setOwner($managedCopy$assoc2);
  2968.                         $prop->setValue($managedCopy$managedCol);
  2969.                     }
  2970.                     if ($assoc2['isCascadeMerge']) {
  2971.                         $managedCol->initialize();
  2972.                         // clear and set dirty a managed collection if its not also the same collection to merge from.
  2973.                         if ( ! $managedCol->isEmpty() && $managedCol !== $mergeCol) {
  2974.                             $managedCol->unwrap()->clear();
  2975.                             $managedCol->setDirty(true);
  2976.                             if ($assoc2['isOwningSide']
  2977.                                 && $assoc2['type'] == ClassMetadata::MANY_TO_MANY
  2978.                                 && $class->isChangeTrackingNotify()
  2979.                             ) {
  2980.                                 $this->scheduleForDirtyCheck($managedCopy);
  2981.                             }
  2982.                         }
  2983.                     }
  2984.                 }
  2985.             }
  2986.             if ($class->isChangeTrackingNotify()) {
  2987.                 // Just treat all properties as changed, there is no other choice.
  2988.                 $this->propertyChanged($managedCopy$namenull$prop->getValue($managedCopy));
  2989.             }
  2990.         }
  2991.     }
  2992.     /**
  2993.      * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
  2994.      * Unit of work able to fire deferred events, related to loading events here.
  2995.      *
  2996.      * @internal should be called internally from object hydrators
  2997.      */
  2998.     public function hydrationComplete()
  2999.     {
  3000.         $this->hydrationCompleteHandler->hydrationComplete();
  3001.     }
  3002.     /**
  3003.      * @param string $entityName
  3004.      */
  3005.     private function clearIdentityMapForEntityName($entityName)
  3006.     {
  3007.         if (! isset($this->identityMap[$entityName])) {
  3008.             return;
  3009.         }
  3010.         $visited = [];
  3011.         foreach ($this->identityMap[$entityName] as $entity) {
  3012.             $this->doDetach($entity$visitedfalse);
  3013.         }
  3014.     }
  3015.     /**
  3016.      * @param string $entityName
  3017.      */
  3018.     private function clearEntityInsertionsForEntityName($entityName)
  3019.     {
  3020.         foreach ($this->entityInsertions as $hash => $entity) {
  3021.             // note: performance optimization - `instanceof` is much faster than a function call
  3022.             if ($entity instanceof $entityName && get_class($entity) === $entityName) {
  3023.                 unset($this->entityInsertions[$hash]);
  3024.             }
  3025.         }
  3026.     }
  3027.     /**
  3028.      * @param ClassMetadata $class
  3029.      * @param mixed         $identifierValue
  3030.      *
  3031.      * @return mixed the identifier after type conversion
  3032.      *
  3033.      * @throws \Doctrine\ORM\Mapping\MappingException if the entity has more than a single identifier
  3034.      */
  3035.     private function convertSingleFieldIdentifierToPHPValue(ClassMetadata $class$identifierValue)
  3036.     {
  3037.         return $this->em->getConnection()->convertToPHPValue(
  3038.             $identifierValue,
  3039.             $class->getTypeOfField($class->getSingleIdentifierFieldName())
  3040.         );
  3041.     }
  3042. }