Created
March 20, 2024 19:15
-
-
Save tito10047/3c48a1e910577f9572ada3c6a98de2d1 to your computer and use it in GitHub Desktop.
Duplicate Doctrine Entity recursive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Service; | |
| use Doctrine\Common\Collections\Collection; | |
| use Doctrine\Persistence\ManagerRegistry; | |
| class DuplicateEntityService { | |
| /** | |
| * @var array<int,object> | |
| */ | |
| public array $clonedObjects = []; | |
| /** | |
| * @var array<int,object> | |
| */ | |
| public array $processedObjects = []; | |
| /** | |
| * @var array<int,callable[]> | |
| */ | |
| public array $callbacks = []; | |
| public function __construct( | |
| private readonly ManagerRegistry $mr | |
| ) {} | |
| public function cloneCollection(Collection $original, Collection $clone, bool $manyToMany = false): Collection { | |
| if ($manyToMany) { | |
| $callback = fn($clonedEntity) => $clone->add($clonedEntity); | |
| } else { | |
| $callback = fn($clonedEntity) => null; | |
| } | |
| $original->map(function ($entity) use ($callback) { | |
| $this->cloneReference($entity, $callback); | |
| }); | |
| return $clone; | |
| } | |
| public function cloneReference(?object $entity, callable $callback): bool { | |
| if ($entity === null) { | |
| $callback(null); | |
| return false; | |
| } | |
| if ($clone = $this->clonedObjects[$entity->getId()->toBinary()]??false) { | |
| $callback($clone); | |
| return false; | |
| } | |
| if (in_array($entity->getId()->toBinary(), $this->processedObjects, true)) { | |
| $this->callbacks[$entity->getId()->toBinary()][] = $callback; | |
| return false; | |
| } | |
| $this->callbacks[$entity->getId()->toBinary()][] = $callback; | |
| $this->processedObjects[] = $entity->getId()->toBinary(); | |
| $repo = $this->mr->getManagerForClass($entity::class)->getRepository($entity::class); | |
| if (!$repo instanceof DuplicateRepository) { | |
| throw new \LogicException("Entity Repository " . get_class($entity) . " need implement DuplicateRepository"); | |
| } | |
| $clone = $repo->duplicate($this, $entity); | |
| $this->clonedObjects[$entity->getId()->toBinary()] = $clone; | |
| foreach ($this->callbacks[$entity->getId()->toBinary()]??[] as $callback) { | |
| $callback($clone); | |
| } | |
| return true; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Service; | |
| interface DuplicateRepository { | |
| public function duplicate(DuplicateEntityService $duplicator, mixed $entity): mixed; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Controller\Admin\Game; | |
| use App\Controller\AbstractController; | |
| use App\Entity\Game\Game; | |
| use App\Service\DuplicateEntityService; | |
| use App\Service\Transaction; | |
| use Symfony\Component\HttpFoundation\Response; | |
| use Symfony\Component\Routing\Attribute\Route; | |
| #[Route('/game', name: 'app.admin.game.')] | |
| class GameController extends AbstractController { | |
| public function __construct(private readonly Transaction $transaction) {} | |
| #[Route(path: '/duplicate/{gameToDuplicate}', name: 'duplicate')] | |
| public function duplicate(DuplicateEntityService $dulicator, Game $gameToDuplicate): Response { | |
| $this->denyAccessUnlessGranted("delete", $gameToDuplicate, "You can't delete this game"); | |
| $this->transaction->begin(); | |
| try { | |
| $dulicator->cloneReference($gameToDuplicate, fn(Game $clone) => null); | |
| $this->transaction->flush(); | |
| $this->transaction->commit(); | |
| } catch (\Throwable $e) { | |
| $this->transaction->rollback(); | |
| throw $e; | |
| } | |
| return $this->redirectToRoute("app.admin.game.list"); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Entity\Game\Speech; | |
| #[ORM\Entity(repositoryClass: SelectorRepository::class)] | |
| class Selector { | |
| #[ORM\Id] | |
| #[ORM\Column(type: UuidType::NAME, unique: true)] | |
| #[ORM\GeneratedValue(strategy: 'CUSTOM')] | |
| #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] | |
| private ?Uuid $id = null; | |
| //... | |
| public function __construct() { | |
| $this->runVisits = new ArrayCollection(); | |
| $this->rpgConstraints = new ArrayCollection(); | |
| $this->speeches = new ArrayCollection(); | |
| } | |
| public function __clone(): void { | |
| $this->id = null; | |
| $this->runVisits = new ArrayCollection(); | |
| $this->rpgConstraints = new ArrayCollection(); | |
| $this->speeches = new ArrayCollection(); | |
| $this->gateway = null; | |
| $this->keyword = null; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Repository\Game\Speech; | |
| use App\Service\DuplicateEntityService; | |
| use App\Service\DuplicateRepository; | |
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | |
| use Doctrine\Persistence\ManagerRegistry; | |
| class SelectorRepository extends ServiceEntityRepository implements DuplicateRepository { | |
| public function __construct(ManagerRegistry $registry) { | |
| parent::__construct($registry, Selector::class); | |
| } | |
| public function save(Selector $entity, bool $flush = false): void { | |
| $this->getEntityManager()->persist($entity); | |
| if ($flush) { | |
| $this->getEntityManager()->flush(); | |
| } | |
| } | |
| /** | |
| * @param DuplicateEntityService $duplicator | |
| * @param Selector $entity | |
| * @return Selector | |
| */ | |
| public function duplicate(DuplicateEntityService $duplicator, mixed $entity): mixed { | |
| $clone = clone $entity; | |
| $this->save($clone); | |
| $duplicator->cloneReference($entity->getParentSpeech(), fn(?Speech $entity) => $clone->setParentSpeech($entity)); | |
| $duplicator->cloneReference($entity->getGateway(), fn(?Gateway $entity) => $clone->setGateway($entity)); | |
| $duplicator->cloneReference($entity->getRoom(), fn(?Room $entity) => $clone->setRoom($entity)); | |
| $duplicator->cloneReference($entity->getKeyword(), fn(?Keyword $entity) => $entity ? $clone->setKeyword($entity) : null); | |
| $duplicator->cloneCollection($entity->getSpeeches(), $clone->getSpeeches(), true); | |
| return $clone; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment