Abstraction for transaction control on an application tier.
composer require sbooker/transaction-manager
use Sbooker\TransactionManager\TransactionHandler;
use Sbooker\TransactionManager\TransactionManager;
$transactionManager = new TransactionManager(new class implements TransactionHandler { ... });
class Entity {
/**
* @throws \Exception
*/
public function update(): void { ... }
}
$transactionManager->transactional(function () use ($transactionManager) {
$transactionManager->persist(new Entity());
});
$transactionManager->transactional(function () use ($transactionManager, $entityId) {
$entity = $transactionManager->getLocked(Entity::class, $entityId);
$entity->update(); // if exception throws, transaction will be rolled back
});
$transactionManager->transactional(function () use ($transactionManager, $entityRepository, $criteria) {
$entity = $entityRepository->getLocked($criteria);
$entity->update(); // if exception throws, transaction will be rolled back
$transactionManager->save($entity);
});
Usually you need only single transaction to process single command (See examples before). Usually you do it in Application Layer service (so-called Command Processor).
final class CommandProcessor {
private TransactionManager $transactionManager;
...
/** @throws \Exception */
public function update($entityId): void
{
$transactionManager->transactional(function () use ($transactionManager, $entityId) {
$entity = $transactionManager->getLocked(Entity::class, $entityId);
$entity->update();
});
}
}
It's work well while you simple call Application Layer from Presentation Layer synchronously. For example using HTTP request and converts it to command in controller.
But sometimes you need process previously stored command with same domain logic as from HTTP request. Of course in this case you want save command execution result. For example, for a next retry if execution fails. In this case you need nested transaction and outer transaction will not be rolled back.
$commandProcessor = new CommandProcessor($transactionManager);
$transactionManager->transactional(function () use ($transactionManager, $commandId, $commandProcessor) {
$command = $transactionManager->getLocked(Command::class, $commandId);
try {
$commandProcessor->update($command->getEntityId());
$command->setSuccessExecutionState();
} catch (\Exception) {
$command->setFailExecutionState();
}
});
Attention!
$entityId = ...;
$transactionManager->transactional(function () use ($transactionManager, $entityId) {
$entity = new SomeEntity($entityId);
$transactionManager->persist($entity);
// Depends on TransactionHandler implementation $persistedEntity may be null in same transaction with persist
$persistedEntity = $transactionManager->getLocked($entityId);
}
See LICENSE file.