Skip to content

Instantly share code, notes, and snippets.

@dadamssg
Last active April 22, 2018 12:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dadamssg/e1bea7329e8bf93c8b04 to your computer and use it in GitHub Desktop.
Save dadamssg/e1bea7329e8bf93c8b04 to your computer and use it in GitHub Desktop.
<?php
namespace Acme\Project\Bundle\AppBundle\Data;
use Acme\Project\Model\App\Data\TransactionManager;
use Doctrine\ORM\EntityManagerInterface;
class DoctrineTransactionManager extends TransactionManager
{
/**
* @var EntityManagerInterface
*/
private $em;
/**
* @param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* {@inheritdoc}
*/
public function begin()
{
$this->getConnection()->beginTransaction();
}
/**
* {@inheritdoc}
*/
public function commit()
{
$this->getConnection()->commit();
}
/**
* {@inheritdoc}
*/
public function rollback()
{
$this->em->close();
$this->getConnection()->rollBack();
}
/**
* @return \Doctrine\DBAL\Connection
*/
private function getConnection()
{
return $this->em->getConnection();
}
}
services:
app.register_user_command_handler:
class: Acme\Project\Model\User\Handler\RegisterUserHandler
arguments:
- @app.user_repository
- @app.password_encoder
tags:
- { name: command_handler, handles: Acme\Project\Model\User\Command\RegisterUserCommand }
- { name: wrap_in_transaction }
<?php
namespace Acme\Project\Bundle\AppBundle\Middleware;
use Acme\Project\Model\App\Data\TransactionManager;
class TransactionalHandler
{
/**
* @var TransactionManager
*/
private $tx;
/**
* @var callable
*/
private $handler;
/**
* @var string
*/
private $method;
/**
* @param TransactionManager $tx
* @param callable $handler
* @param string $method
*/
public function __construct(TransactionManager $tx, $handler, $method = null)
{
$this->tx = $tx;
$this->handler = $handler;
$this->method = $method;
}
public function handle($command)
{
$method = $this->method ?: '__invoke';
$method = method_exists($this->handler, $method) ? $method : 'handle';
if (!method_exists($this->handler, $method)) {
throw new \Exception("Command handler has no method to handle command.");
}
$this->tx->transactional(function () use ($command, $method) {
$this->handler->$method($command);
});
}
}
<?php
namespace Acme\Project\Bundle\AppBundle\DependencyInjection\Compiler;
use Acme\Project\Bundle\AppBundle\Data\DoctrineTransactionManager;
use Acme\Project\Bundle\AppBundle\Middleware\TransactionalHandler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class WrapHandlerInTransactionCompilerPass implements CompilerPassInterface
{
const TRANSACTION_MANAGER_ID = 'app.transaction_manager';
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->registerTransactionManager($container);
$taggedServices = $container->findTaggedServiceIds('wrap_in_transaction');
foreach ($taggedServices as $id => $tags) {
$def = $container->findDefinition($id);
$commandHandlerTags = $def->getTag('command_handler');
if (!$commandHandlerTags) {
throw new \Exception(sprintf('Service "%s" must be tagged as a command handler to be wrapped in a transaction.', $id));
}
// remove command_handler tags so handler isn't
// directly interacted with by message bus
$def->clearTag('command_handler');
$originalDefId = $this->renameOriginalDefinition($container, $def, $id);
// A single handler could be responsible for handling different
// commands via different methods so we create new handlers
// for each handler::method
foreach ($commandHandlerTags as $tag) {
$handlerMethod = isset($tag['method']) ? $tag['method'] : null;
// set method so TransactionalHandler::handle() will be called
$tag['method'] = 'handle';
$container->register($id, TransactionalHandler::CLASS)
->addArgument(new Reference(self::TRANSACTION_MANAGER_ID))
->addArgument(new Reference($originalDefId))
->addArgument($handlerMethod)
->addTag('command_handler', $tag);
}
}
}
/**
* @param ContainerBuilder $container
*/
private function registerTransactionManager(ContainerBuilder $container)
{
$emId = sprintf("doctrine.orm.%s_entity_manager", $container->getParameter('command_bus_em_name'));
$container->register(self::TRANSACTION_MANAGER_ID, DoctrineTransactionManager::CLASS)
->addArgument(new Reference($emId));
}
/**
* @param ContainerBuilder $container
* @param Definition$def
* @param string $id
* @return string
*/
public function renameOriginalDefinition(ContainerBuilder $container, Definition $def, $id)
{
$container->removeDefinition($id);
$originalDefId = sprintf("tx_wrapped.%s", $id);
$container->setDefinition($originalDefId, $def);
return $originalDefId;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment