Skip to content

Instantly share code, notes, and snippets.

@tito10047
Created March 20, 2024 19:15
Show Gist options
  • Select an option

  • Save tito10047/3c48a1e910577f9572ada3c6a98de2d1 to your computer and use it in GitHub Desktop.

Select an option

Save tito10047/3c48a1e910577f9572ada3c6a98de2d1 to your computer and use it in GitHub Desktop.
Duplicate Doctrine Entity recursive
<?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;
}
}
<?php
namespace App\Service;
interface DuplicateRepository {
public function duplicate(DuplicateEntityService $duplicator, mixed $entity): mixed;
}
<?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");
}
}
<?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;
}
}
<?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