From df33eec30f6c84f4af203aae646f060305d90b24 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 11 Feb 2025 15:06:04 +0100 Subject: [PATCH] WIP merge service --- .../AccompanyingPeriodWorkMergeService.php | 193 +++++++----------- 1 file changed, 73 insertions(+), 120 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php b/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php index 4b48ffcbf..155a180a7 100644 --- a/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php +++ b/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php @@ -12,7 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Service\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; -use Doctrine\DBAL\Exception; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; @@ -20,9 +20,6 @@ class AccompanyingPeriodWorkMergeService { public function __construct(private readonly EntityManagerInterface $em) {} - /** - * @throws Exception - */ public function merge(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void { // Transfer non-duplicate data @@ -35,140 +32,96 @@ class AccompanyingPeriodWorkMergeService $this->em->flush(); } - /** - * @throws Exception - */ private function transferData(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void { - $conn = $this->em->getConnection(); + $excludedProperties = ['id', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy']; + $reflection = new \ReflectionClass(AccompanyingPeriodWork::class); - $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) { + foreach ($reflection->getProperties() as $property) { + if (in_array($property->getName(), $excludedProperties, true)) { continue; } + $toKeepValue = $property->getValue($toKeep); + $toDeleteValue = $property->getValue($toDelete); + + if (null === $toKeepValue && null !== $toDeleteValue) { + $property->setValue($toKeep, $toDeleteValue); + } + + if ($toKeepValue instanceof Collection + && $toDeleteValue instanceof Collection) { + foreach ($toDeleteValue as $item) { + if (!$toKeepValue->contains($item)) { + $toKeepValue->add($item); + } + } + } + } + } + + private function updateReferences(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void + { + $allMeta = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($allMeta as $meta) { foreach ($meta->getAssociationMappings() as $assoc) { if (AccompanyingPeriodWork::class !== $assoc['targetEntity']) { - continue; + continue; // Skip unrelated associations } + $entityClass = $meta->getName(); + $associationField = $assoc['fieldName']; + if ($assoc['type'] & ClassMetadata::TO_ONE) { - if ('handlingThirdparty' === $assoc['fieldName']) { - continue; - } - $sqlStatements[] = $this->generateToOneUpdateQuery($meta, $assoc); + // Handle ManyToOne or OneToOne + $qb = $this->em->createQueryBuilder(); + $qb->update($entityClass, 'e') + ->set("e.{$associationField}", ':toKeep') + ->where("e.{$associationField} = :toDelete") + ->setParameter('toKeep', $toKeep) + ->setParameter('toDelete', $toDelete) + ->getQuery() + ->execute(); } if ($assoc['type'] & ClassMetadata::TO_MANY) { - if (!isset($assoc['joinTable'])) { - continue; + // Handle ManyToMany or OneToMany (inverse side) + $repo = $this->em->getRepository($entityClass); + $linkedEntities = $repo->createQueryBuilder('e') + ->join("e.{$associationField}", 't') + ->where('t = :toDelete') + ->setParameter('toDelete', $toDelete) + ->getQuery() + ->getResult(); + + foreach ($linkedEntities as $entity) { + $getter = 'get'.ucfirst($associationField); + $setter = 'set'.ucfirst($associationField); + $adder = 'add'.ucfirst(rtrim($associationField, 's')); + $remover = 'remove'.ucfirst(rtrim($associationField, 's')); + + if (method_exists($entity, $getter) && method_exists($entity, $setter)) { + // For OneToMany owning side + $collection = $entity->{$getter}(); + if ($collection->contains($toDelete)) { + $collection->removeElement($toDelete); + if (!$collection->contains($toKeep)) { + $collection->add($toKeep); + } + } + } elseif (method_exists($entity, $adder) && method_exists($entity, $remover)) { + // For ManyToMany + $entity->{$remover}($toDelete); + $entity->{$adder}($toKeep); + } + + $this->em->persist($entity); } - $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; + $this->em->flush(); } }