private function computeOrRecomputeChangeSet(ClassMetadata $class, $document, $recompute = false)
{
$oid = spl_object_hash($document);
$actualData = $this->getDocumentActualData($document);
$isNewDocument = !isset($this->originalDocumentData[$oid]);
if ($isNewDocument) {
// Document is either NEW or MANAGED but not yet fully persisted (only has an id).
// These result in an INSERT.
$this->originalDocumentData[$oid] = $actualData;
$changeSet = array();
foreach ($actualData as $propName => $actualValue) {
/* At this PersistentCollection shouldn't be here, probably it
* was cloned and its ownership must be fixed
*/
if ($actualValue instanceof PersistentCollectionInterface && $actualValue->getOwner() !== $document) {
$actualData[$propName] = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
$actualValue = $actualData[$propName];
}
// ignore inverse side of reference relationship
if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['isInverseSide']) {
continue;
}
$changeSet[$propName] = array(null, $actualValue);
}
$this->documentChangeSets[$oid] = $changeSet;
} else {
// Document is "fully" MANAGED: it was already fully persisted before
// and we have a copy of the original data
$originalData = $this->originalDocumentData[$oid];
$isChangeTrackingNotify = $class->isChangeTrackingNotify();
if ($isChangeTrackingNotify && !$recompute && isset($this->documentChangeSets[$oid])) {
$changeSet = $this->documentChangeSets[$oid];
} else {
$changeSet = array();
}
foreach ($actualData as $propName => $actualValue) {
// skip not saved fields
if (isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) {
continue;
}
$orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
// skip if value has not changed
if ($orgValue === $actualValue) {
if ($actualValue instanceof PersistentCollectionInterface) {
if (!$actualValue->isDirty() && !$this->isCollectionScheduledForDeletion($actualValue)) {
// consider dirty collections as changed as well
continue;
}
} elseif (!(isset($class->fieldMappings[$propName]['file']) && $actualValue->isDirty())) {
// but consider dirty GridFSFile instances as changed
continue;
}
}
// if relationship is a embed-one, schedule orphan removal to trigger cascade remove operations
if (isset($class->fieldMappings[$propName]['embedded']) && $class->fieldMappings[$propName]['type'] === 'one') {
if ($orgValue !== null) {
$this->scheduleOrphanRemoval($orgValue);
}
$changeSet[$propName] = array($orgValue, $actualValue);
continue;
}
// if owning side of reference-one relationship
if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'one' && $class->fieldMappings[$propName]['isOwningSide']) {
if ($orgValue !== null && $class->fieldMappings[$propName]['orphanRemoval']) {
$this->scheduleOrphanRemoval($orgValue);
}
$changeSet[$propName] = array($orgValue, $actualValue);
continue;
}
if ($isChangeTrackingNotify) {
continue;
}
// ignore inverse side of reference relationship
if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['isInverseSide']) {
continue;
}
// Persistent collection was exchanged with the "originally"
// created one. This can only mean it was cloned and replaced
// on another document.
if ($actualValue instanceof PersistentCollectionInterface && $actualValue->getOwner() !== $document) {
$actualValue = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
}
// if embed-many or reference-many relationship
if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'many') {
$changeSet[$propName] = array($orgValue, $actualValue);
/* If original collection was exchanged with a non-empty value
* and $set will be issued, there is no need to $unset it first
*/
if ($actualValue && $actualValue->isDirty() && CollectionHelper::usesSet($class->fieldMappings[$propName]['strategy'])) {
continue;
}
if ($orgValue !== $actualValue && $orgValue instanceof PersistentCollectionInterface) {
$this->scheduleCollectionDeletion($orgValue);
}
continue;
}
// skip equivalent date values
if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'date') {
$dateType = Type::getType('date');
$dbOrgValue = $dateType->convertToDatabaseValue($orgValue);
$dbActualValue = $dateType->convertToDatabaseValue($actualValue);
if ($dbOrgValue instanceof \MongoDate && $dbActualValue instanceof \MongoDate && $dbOrgValue == $dbActualValue) {
continue;
}
}
// regular field
$changeSet[$propName] = array($orgValue, $actualValue);
}
if ($changeSet) {
$this->documentChangeSets[$oid] = isset($this->documentChangeSets[$oid]) ? $changeSet + $this->documentChangeSets[$oid] : $changeSet;
$this->originalDocumentData[$oid] = $actualData;
$this->scheduleForUpdate($document);
}
}
// Look for changes in associations of the document
$associationMappings = array_filter($class->associationMappings, function ($assoc) {
return empty($assoc['notSaved']);
});
foreach ($associationMappings as $mapping) {
$value = $class->reflFields[$mapping['fieldName']]->getValue($document);
if ($value === null) {
continue;
}
$this->computeAssociationChanges($document, $mapping, $value);
if (isset($mapping['reference'])) {
continue;
}
$values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value->unwrap();
foreach ($values as $obj) {
$oid2 = spl_object_hash($obj);
if (isset($this->documentChangeSets[$oid2])) {
if (empty($this->documentChangeSets[$oid][$mapping['fieldName']])) {
// instance of $value is the same as it was previously otherwise there would be
// change set already in place
$this->documentChangeSets[$oid][$mapping['fieldName']] = array($value, $value);
}
if (!$isNewDocument) {
$this->scheduleForUpdate($document);
}
break;
}
}
}
}