Commit 96f4d511 authored by romanb's avatar romanb

[2.0] Improved changeset detection.

parent a6d9236e
...@@ -351,4 +351,14 @@ class OraclePlatform extends AbstractPlatform ...@@ -351,4 +351,14 @@ class OraclePlatform extends AbstractPlatform
return parent::_getTransactionIsolationLevelSql($level); return parent::_getTransactionIsolationLevelSql($level);
} }
} }
/**
* Whether the platform prefers sequences for ID generation.
*
* @return boolean
*/
public function prefersSequences()
{
return true;
}
} }
\ No newline at end of file
...@@ -305,16 +305,23 @@ class UnitOfWork implements PropertyChangedListener ...@@ -305,16 +305,23 @@ class UnitOfWork implements PropertyChangedListener
public function computeChangeSets(array $entities = null) public function computeChangeSets(array $entities = null)
{ {
$entitySet = array(); $entitySet = array();
$newEntities = array();
if ($entities !== null) { if ($entities !== null) {
foreach ($entities as $entity) { foreach ($entities as $entity) {
$entitySet[get_class($entity)][] = $entity; $entitySet[get_class($entity)][] = $entity;
} }
$newEntities = $entities;
} else { } else {
$entitySet = $this->_identityMap; $entitySet = $this->_identityMap;
$newEntities = $this->_entityInsertions;
} }
//TODO: Compute changesets for NEW entities first here // Compute changes for NEW entities first. This must always happen.
foreach ($newEntities as $entity) {
$this->_computeEntityChanges($this->_em->getClassMetadata(get_class($entity)), $entity);
}
// Compute changes for MANAGED entities. Change tracking policies take effect here.
foreach ($entitySet as $className => $entities) { foreach ($entitySet as $className => $entities) {
$class = $this->_em->getClassMetadata($className); $class = $this->_em->getClassMetadata($className);
...@@ -328,97 +335,126 @@ class UnitOfWork implements PropertyChangedListener ...@@ -328,97 +335,126 @@ class UnitOfWork implements PropertyChangedListener
$this->_scheduledForDirtyCheck[$className] : $entities; $this->_scheduledForDirtyCheck[$className] : $entities;
foreach ($entitiesToProcess as $entity) { foreach ($entitiesToProcess as $entity) {
$oid = spl_object_hash($entity); // Only MANAGED entities are processed here.
$state = $this->getEntityState($entity); if ($this->getEntityState($entity) == self::STATE_MANAGED) {
$this->_computeEntityChanges($class, $entity);
if ( ! $class->isInheritanceTypeNone()) { // Look for changes in associations of the entity
$class = $this->_em->getClassMetadata(get_class($entity)); foreach ($class->getAssociationMappings() as $assoc) {
} $val = $class->getReflectionProperty($assoc->getSourceFieldName())->getValue($entity);
if ($val !== null) {
// Look for changes in the entity itself by comparing against the $this->_computeAssociationChanges($assoc, $val);
// original data we have.
if ($state == self::STATE_MANAGED || $state == self::STATE_NEW) {
$actualData = array();
foreach ($class->getReflectionProperties() 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->getAssociationMapping($name);
echo PHP_EOL . "INJECTING PCOLL into $name" . PHP_EOL;
// Inject PersistentCollection
$coll = new PersistentCollection($this->_em, $assoc->getTargetEntityName(),
$actualData[$name] ? $actualData[$name] : array());
$coll->setOwner($entity, $assoc);
if ( ! $coll->isEmpty()) $coll->setDirty(true);
$class->getReflectionProperty($name)->setValue($entity, $coll);
$actualData[$name] = $coll;
} }
} }
}
}
}
}
if ( ! isset($this->_originalEntityData[$oid])) { /**
// Entity is either NEW or MANAGED but not yet fully persisted * Computes the changes done to a single entity.
// (only has an id). These result in an INSERT. *
$this->_originalEntityData[$oid] = $actualData; * Modifies/populates the following properties:
$this->_entityChangeSets[$oid] = array_map( *
function($e) { return array(null, $e); }, * {@link _originalEntityData}
$actualData * 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
} else { * entity data yet. All of the current entity data is stored as the original entity data.
// Entity is "fully" MANAGED: it was already fully persisted before *
// and we have a copy of the original data * {@link _entityChangeSets}
$originalData = $this->_originalEntityData[$oid]; * The changes detected on all properties of the entity are stored there.
$changeSet = array(); * A change is a tuple array where the first entry is the old value and the second
$entityIsDirty = false; * entry is the new value of the property. Changesets are used by persisters
* to INSERT/UPDATE the persistent entity state.
foreach ($actualData as $propName => $actualValue) { *
$orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; * {@link _entityUpdates}
if (is_object($orgValue) && $orgValue !== $actualValue) { * If the entity is already fully MANAGED (has been fetched from the database before)
$changeSet[$propName] = array($orgValue, $actualValue); * and any changes to its properties are detected, then a reference to the entity is stored
} else if ($orgValue != $actualValue || ($orgValue === null xor $actualValue === null)) { * there to mark it for an update.
$changeSet[$propName] = array($orgValue, $actualValue); *
} * {@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->getReflectionProperties() as $name => $refProp) {
if ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) {
$actualData[$name] = $refProp->getValue($entity);
}
if (isset($changeSet[$propName])) { if ($class->isCollectionValuedAssociation($name)
if ($class->hasAssociation($propName)) { && $actualData[$name] !== null
$assoc = $class->getAssociationMapping($propName); && ! ($actualData[$name] instanceof PersistentCollection)
if ($assoc->isOneToOne() && $assoc->isOwningSide()) { ) {
$entityIsDirty = true; //TODO: If $actualData[$name] is Collection then unwrap the array
} else if ($orgValue instanceof PersistentCollection) { $assoc = $class->getAssociationMapping($name);
// A PersistentCollection was de-referenced, so delete it. echo PHP_EOL . "INJECTING PCOLL into $name" . PHP_EOL;
if ( ! in_array($orgValue, $this->_collectionDeletions, true)) { // Inject PersistentCollection
$this->_collectionDeletions[] = $orgValue; $coll = new PersistentCollection($this->_em, $assoc->getTargetEntityName(),
} $actualData[$name] ? $actualData[$name] : array());
} $coll->setOwner($entity, $assoc);
} else { if ( ! $coll->isEmpty()) $coll->setDirty(true);
$entityIsDirty = true; $class->getReflectionProperty($name)->setValue($entity, $coll);
} $actualData[$name] = $coll;
} }
} }
if ($changeSet) {
if ($entityIsDirty) {
$this->_entityUpdates[$oid] = $entity;
}
$this->_entityChangeSets[$oid] = $changeSet;
$this->_originalEntityData[$oid] = $actualData;
}
}
// Look for changes in associations of the entity if ( ! isset($this->_originalEntityData[$oid])) {
if ($state == self::STATE_MANAGED) { // Entity is either NEW or MANAGED but not yet fully persisted
foreach ($class->getAssociationMappings() as $assoc) { // (only has an id). These result in an INSERT.
$val = $actualData[$assoc->getSourceFieldName()]; $this->_originalEntityData[$oid] = $actualData;
if ($val !== null) { $this->_entityChangeSets[$oid] = array_map(
$this->_computeAssociationChanges($assoc, $val); 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 xor $actualValue === null)) {
$changeSet[$propName] = array($orgValue, $actualValue);
}
if (isset($changeSet[$propName])) {
if ($class->hasAssociation($propName)) {
$assoc = $class->getAssociationMapping($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;
}
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment