/** * Computes the changes done to a single entity. * * Modifies/populates the following properties: * * {@link _originalEntityData} * If the entity is NEW or MANAGED but not yet fully persisted (only has an id) * then it was not fetched from the database and therefore we have no original * entity data yet. All of the current entity data is stored as the original entity data. * * {@link _entityChangeSets} * The changes detected on all properties of the entity are stored there. * A change is a tuple array where the first entry is the old value and the second * entry is the new value of the property. Changesets are used by persisters * to INSERT/UPDATE the persistent entity state. * * {@link _entityUpdates} * If the entity is already fully MANAGED (has been fetched from the database before) * and any changes to its properties are detected, then a reference to the entity is stored * there to mark it for an update. * * {@link _collectionDeletions} * If a PersistentCollection has been de-referenced in a fully MANAGED entity, * then this collection is marked for deletion. * * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to compute the changes. */ private function _computeEntityChanges($class, $entity) { $oid = spl_object_hash($entity); if (!$class->isInheritanceTypeNone()) { $class = $this->_em->getClassMetadata(get_class($entity)); } $actualData = array(); foreach ($class->reflFields as $name => $refProp) { if (!$class->isIdentifier($name) || !$class->isIdGeneratorIdentity()) { $actualData[$name] = $refProp->getValue($entity); } if ($class->isCollectionValuedAssociation($name) && $actualData[$name] !== null && !$actualData[$name] instanceof PersistentCollection) { //TODO: If $actualData[$name] is Collection then unwrap the array $assoc = $class->associationMappings[$name]; //echo PHP_EOL . "INJECTING PCOLL into $name" . PHP_EOL; // Inject PersistentCollection $coll = new PersistentCollection($this->_em, $this->_em->getClassMetadata($assoc->targetEntityName), $actualData[$name] ? $actualData[$name] : array()); $coll->setOwner($entity, $assoc); if (!$coll->isEmpty()) { $coll->setDirty(true); } $class->reflFields[$name]->setValue($entity, $coll); $actualData[$name] = $coll; } } if (!isset($this->_originalEntityData[$oid])) { // Entity is either NEW or MANAGED but not yet fully persisted // (only has an id). These result in an INSERT. $this->_originalEntityData[$oid] = $actualData; $this->_entityChangeSets[$oid] = array_map(function ($e) { return array(null, $e); }, $actualData); } else { // Entity is "fully" MANAGED: it was already fully persisted before // and we have a copy of the original data $originalData = $this->_originalEntityData[$oid]; $changeSet = array(); $entityIsDirty = false; foreach ($actualData as $propName => $actualValue) { $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; if (is_object($orgValue) && $orgValue !== $actualValue) { $changeSet[$propName] = array($orgValue, $actualValue); } else { if ($orgValue != $actualValue || $orgValue === null ^ $actualValue === null) { $changeSet[$propName] = array($orgValue, $actualValue); } } if (isset($changeSet[$propName])) { if (isset($class->associationMappings[$propName])) { $assoc = $class->associationMappings[$propName]; if ($assoc->isOneToOne() && $assoc->isOwningSide) { $entityIsDirty = true; } else { if ($orgValue instanceof PersistentCollection) { // A PersistentCollection was de-referenced, so delete it. if (!in_array($orgValue, $this->_collectionDeletions, true)) { $this->_collectionDeletions[] = $orgValue; } } } } else { $entityIsDirty = true; } } } if ($changeSet) { if ($entityIsDirty) { $this->_entityUpdates[$oid] = $entity; } $this->_entityChangeSets[$oid] = $changeSet; $this->_originalEntityData[$oid] = $actualData; } } }
/** * INTERNAL: * Computes the changeset of an individual entity, independently of the * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). * * The passed entity must be a managed entity. If the entity already has a change set * because this method is invoked during a commit cycle then the change sets are added. * whereby changes detected in this method prevail. * * @ignore * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to (re)calculate the change set. * @throws InvalidArgumentException If the passed entity is not MANAGED. */ public function recomputeSingleEntityChangeSet($class, $entity) { $oid = spl_object_hash($entity); if (!isset($this->_entityStates[$oid]) || $this->_entityStates[$oid] != self::STATE_MANAGED) { throw new \InvalidArgumentException('Entity must be managed.'); } /* TODO: Just return if changetracking policy is NOTIFY? if ($class->isChangeTrackingNotify()) { return; }*/ if (!$class->isInheritanceTypeNone()) { $class = $this->_em->getClassMetadata(get_class($entity)); } $actualData = array(); foreach ($class->reflFields as $name => $refProp) { if (!$class->isIdentifier($name) || !$class->isIdGeneratorIdentity()) { $actualData[$name] = $refProp->getValue($entity); } } $originalData = $this->_originalEntityData[$oid]; $changeSet = array(); foreach ($actualData as $propName => $actualValue) { $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; if (is_object($orgValue) && $orgValue !== $actualValue) { $changeSet[$propName] = array($orgValue, $actualValue); } else { if ($orgValue != $actualValue || $orgValue === null ^ $actualValue === null) { $changeSet[$propName] = array($orgValue, $actualValue); } } } if ($changeSet) { if (isset($this->_entityChangeSets[$oid])) { $this->_entityChangeSets[$oid] = $changeSet + $this->_entityChangeSets[$oid]; } $this->_originalEntityData[$oid] = $actualData; } }