transferData($toKeep, $toDelete); // Update linked entities $this->updateReferences($toKeep, $toDelete); $this->em->remove($toDelete); $this->em->flush(); } /** * @throws Exception */ private function transferData(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void { $conn = $this->em->getConnection(); $sqlStatements = [ $this->generateStartDateSQL(), $this->generateEndDateSQL(), $this->generateCommentSQL(), ]; $conn->beginTransaction(); try { foreach ($sqlStatements as $sql) { $conn->executeQuery($sql, ['toDelete' => $toDelete->getId(), 'toKeep' => $toKeep->getId()]); } $conn->commit(); } catch (\Exception $e) { $conn->rollBack(); throw $e; } } private function generateStartDateSQL(): string { return ' UPDATE chill_person_accompanying_period_work SET startdate = LEAST( COALESCE((SELECT startdate FROM chill_person_accompanying_period_work WHERE id = :toDelete), startdate), startdate ) WHERE id = :toKeep'; } private function generateEndDateSQL(): string { return ' UPDATE chill_person_accompanying_period_work SET enddate = CASE WHEN (SELECT enddate FROM chill_person_accompanying_period_work WHERE id = :toDelete) IS NULL OR enddate IS NULL THEN NULL ELSE GREATEST( COALESCE((SELECT enddate FROM chill_person_accompanying_period_work WHERE id = :toDelete), enddate), enddate ) END WHERE id = :toKeep'; } private function generateCommentSQL(): string { return " UPDATE chill_person_accompanying_period_work SET note = CONCAT_WS( '\n', NULLIF(TRIM(note), ''), NULLIF(TRIM((SELECT note FROM chill_person_accompanying_period_work WHERE id = :toDelete)), '') ) WHERE id = :toKeep"; } /** * @throws Exception */ private function updateReferences(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void { $allMeta = $this->em->getMetadataFactory()->getAllMetadata(); $conn = $this->em->getConnection(); $sqlStatements = []; foreach ($allMeta as $meta) { if ($meta->isMappedSuperclass) { continue; } foreach ($meta->getAssociationMappings() as $assoc) { if (AccompanyingPeriodWork::class !== $assoc['targetEntity']) { continue; } if ($assoc['type'] & ClassMetadata::TO_ONE) { if ('handlingThirdparty' === $assoc['fieldName']) { continue; } $sqlStatements[] = $this->generateToOneUpdateQuery($meta, $assoc); } if ($assoc['type'] & ClassMetadata::TO_MANY) { if (!isset($assoc['joinTable'])) { continue; } $sqlStatements = array_merge($sqlStatements, $this->generateToManyUpdateQueries($assoc)); } } } $conn->beginTransaction(); try { foreach ($sqlStatements as $sql) { $conn->executeStatement($sql, ['toDelete' => $toDelete->getId(), 'toKeep' => $toKeep->getId()]); } $conn->commit(); } catch (\Exception $e) { $conn->rollBack(); throw $e; } } private function generateToOneUpdateQuery(ClassMetadata $meta, array $assoc): string { $tableName = $meta->getTableName(); $joinColumn = $meta->getSingleAssociationJoinColumnName($assoc['fieldName']); return "UPDATE {$tableName} SET {$joinColumn} = :toKeep WHERE {$joinColumn} = :toDelete"; } private function generateToManyUpdateQueries(array $assoc): array { $sqls = []; $joinTable = $assoc['joinTable']['name']; $owningColumn = $assoc['joinTable']['joinColumns'][0]['name']; $inverseColumn = $assoc['joinTable']['inverseJoinColumns'][0]['name']; // Insert relations, skip already existing ones $sqls[] = "INSERT IGNORE INTO {$joinTable} ({$owningColumn}, {$inverseColumn}) SELECT :toKeep, {$inverseColumn} FROM {$joinTable} WHERE {$owningColumn} = :toDelete"; // Delete old references $sqls[] = "DELETE FROM {$joinTable} WHERE {$owningColumn} = :toDelete"; return $sqls; } }