private function syncReferences(array $referencesToUpdate)
{
if ($referencesToUpdate) {
// do not update references that are going to be deleted anyways
$toUpdate = array_diff(array_keys($referencesToUpdate), array_keys($this->referencesToDelete));
try {
foreach ($this->referenceTables as $table) {
$query = "DELETE FROM {$table} WHERE source_id IN (?)";
$this->executeChunkedUpdate($query, $toUpdate);
}
} catch (DBALException $e) {
throw new RepositoryException('Unexpected exception while cleaning up after saving', $e->getCode(), $e);
}
$updates = array();
foreach ($toUpdate as $nodeId) {
$references = $referencesToUpdate[$nodeId];
foreach ($references['properties'] as $name => $data) {
foreach ($data['values'] as $value) {
$targetId = $this->getSystemIdForNode($value);
if (false === $targetId) {
if (PropertyType::REFERENCE === $data['type']) {
throw new ReferentialIntegrityException(sprintf('Trying to store reference to non-existant node with path "%s" in node "%s" "%s"', $value, $references['path'], $name));
}
continue;
}
$key = $targetId . '-' . $nodeId . '-' . $name;
// it is valid to have multiple references to the same node in a multivalue
// but it is not desired to store duplicates in the database
$updates[$key] = array('type' => $data['type'], 'data' => array('source_id' => $nodeId, 'source_property_name' => $name, 'target_id' => $targetId));
}
}
}
foreach ($updates as $update) {
$this->getConnection()->insert($this->referenceTables[$update['type']], $update['data']);
}
}
// TODO on RDBMS that support deferred FKs we could skip this step
if ($this->referencesToDelete) {
$params = array_keys($this->referencesToDelete);
// remove all PropertyType::REFERENCE with a source_id on a deleted node
try {
$query = "DELETE FROM phpcr_nodes_references WHERE source_id IN (?)";
$this->executeChunkedUpdate($query, $params);
} catch (DBALException $e) {
throw new RepositoryException('Unexpected exception while cleaning up deleted nodes', $e->getCode(), $e);
}
// ensure that there are no PropertyType::REFERENCE pointing to nodes that will be deleted
// Note: due to the outer join we cannot filter on workspace_name, but this is ok
// since within a transaction there can never be missing referenced nodes within the current workspace
// make sure the target node is not in the list of nodes being deleted, to allow deletion in same request
$query = 'SELECT DISTINCT r.target_id
FROM phpcr_nodes_references r
LEFT OUTER JOIN phpcr_nodes n ON r.target_id = n.id
WHERE r.target_id IN (?)';
if ($this->getConnection()->getDatabasePlatform() instanceof SqlitePlatform) {
$missingTargets = array();
foreach (array_chunk($params, self::SQLITE_MAXIMUM_IN_PARAM_COUNT) as $chunk) {
$stmt = $this->getConnection()->executeQuery($query, array($chunk), array(Connection::PARAM_INT_ARRAY));
$missingTargets = array_merge($missingTargets, $stmt->fetchAll(\PDO::FETCH_COLUMN));
}
} else {
$stmt = $this->getConnection()->executeQuery($query, array($params), array(Connection::PARAM_INT_ARRAY));
$missingTargets = $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
if ($missingTargets) {
$paths = array();
foreach ($missingTargets as $id) {
if (isset($this->referencesToDelete[$id])) {
$paths[] = $this->referencesToDelete[$id];
}
}
throw new ReferentialIntegrityException("Cannot delete '" . implode("', '", $paths) . "': A reference points to this node or a subnode");
}
// clean up all references
try {
foreach ($this->referenceTables as $table) {
$query = "DELETE FROM {$table} WHERE target_id IN (?)";
$this->executeChunkedUpdate($query, $params);
}
} catch (DBALException $e) {
throw new RepositoryException('Unexpected exception while cleaning up deleted nodes', $e->getCode(), $e);
}
}
}