Skip to content

Instantly share code, notes, and snippets.

@klkvsk
Created April 17, 2024 11:17
Show Gist options
  • Save klkvsk/f3ad81c274968144e3e0ac34732f1696 to your computer and use it in GitHub Desktop.
Save klkvsk/f3ad81c274968144e3e0ac34732f1696 to your computer and use it in GitHub Desktop.
UniqueSafeRepositoryTrait
<?php
namespace App\Framework\Doctrine;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
trait UniqueSafeRepositoryTrait
{
/**
* @template T of object
* @param array $uniqueData
* @param callable():T $factory
* @return object
* @psalm-return T
*/
public function findOrCreate(array $uniqueData, callable $factory): object
{
assert($this instanceof EntityRepository);
// optimistic read
$entity = $this->findOneBy($uniqueData);
if ($entity) {
return $entity;
}
// try inserting
$entity = $factory();
$this->uniqueSafeInsert($entity);
// retry read if write failed
$entity = $this->findOneBy($uniqueData);
if (!$entity) {
throw new \LogicException('Failed to find existing unique entity: ' . json_encode($uniqueData));
}
return $entity;
}
protected function uniqueSafeInsert(object $entity): bool
{
assert($this instanceof EntityRepository);
$em = $this->getEntityManager();
$classMetadata = $em->getClassMetadata($entity::class);
// prepare insert data
$em->persist($entity);
$em->getUnitOfWork()->computeChangeSet($classMetadata, $entity);
// manually insert single entity via separate persister
$entityPersister = new BasicEntityPersister($em, $classMetadata);
$entityPersister->addInsert($entity);
try {
$entityPersister->executeInserts();
return true;
} catch (UniqueConstraintViolationException $e) {
return false;
} finally {
// cleanup so it won't be persisted on next flush
$em->detach($entity);
$em->getUnitOfWork()->clearEntityChangeSet(spl_object_id($entity));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment