chill-bundles/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php

175 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Service\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Doctrine\DBAL\Exception;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
class AccompanyingPeriodWorkMergeService
{
public function __construct(private readonly EntityManagerInterface $em) {}
/**
* @throws Exception
*/
public function merge(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void
{
// Transfer non-duplicate data
$this->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;
}
}