Skip to content

Instantly share code, notes, and snippets.

@bizley
Created September 4, 2022 11:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bizley/15f9be893ded796f4d0c68dea5e3efa5 to your computer and use it in GitHub Desktop.
Save bizley/15f9be893ded796f4d0c68dea5e3efa5 to your computer and use it in GitHub Desktop.
Thread-safe business logic with Doctrine
<?php
namespace FavreBenjamin\Utils;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\DBAL\Exception\RetryableException;
use Doctrine\ORM\EntityManagerInterface;
class CustomEntityManager
{
private $em;
private $mr;
public function __construct(EntityManagerInterface $em,
ManagerRegistry $mr)
{
$this->em = $em;
$this->mr = $mr;
}
public function transactional(callable $callback)
{
$retries = 0;
do {
$this->beginTransaction();
try {
$ret = $callback();
$this->flush();
$this->commit();
return $ret;
} catch (RetryableException $e) {
$this->rollback();
$this->close();
$this->resetManager();
++$retries;
} catch (\Exception $e) {
$this->rollback();
throw $e;
}
} while ($retries < 10);
throw $e;
}
public function resetManager()
{
$this->em = $this->mr->resetManager();
}
public function __call($name, $args) {
return call_user_func_array([$this->em, $name], $args);
}
}
<?php
use FavreBenjamin\Utils\CustomEntityManager;
use MyProject\Entity\Account;
use Doctrine\DBAL\LockMode;
class MyBusinessLogicService
{
private $em;
public function __construct(CustomEntityManager $em)
{
$this->em = $em;
}
public function debit(int $accountId, int $amount): bool
{
$callback = function() use ($accountId, $amount) {
// Get repository inside callable to make sure EntityManager is valid
$accounts = $this->em->getRepository(Account::class);
// Fetch account with FOR UPDATE write lock
$account = $accounts->find(
$accountId,
LockMode::PESSIMISTIC_WRITE
);
// We are protected from concurrent access here
if ($account->amount < $amount)
return false;
$account->amount -= $amount;
return true;
});
return $this->em->transactional($callback);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment