sbooker / transaction-manager
Abstraction for transaction control on an application tier.
Installs: 6 648
Dependents: 6
Suggesters: 0
Security: 0
Stars: 1
Watchers: 2
Forks: 1
Open Issues: 1
Requires
- php: ^7.4 || ^8.0
Requires (Dev)
- phpunit/phpunit: ^9.5
Suggests
- sbooker/doctrine-transaction-handler: For usage with Doctrine ORM
- sbooker/yii2-ar-transaction-handler: For usage with Yii2 Active Record
README
Abstraction for transaction control on an application tier.
Installation
composer require sbooker/transaction-manager
Example of usage
use Sbooker\TransactionManager\TransactionHandler; use Sbooker\TransactionManager\TransactionManager; $transactionManager = new TransactionManager(new class implements TransactionHandler { ... }); class Entity { /** * @throws \Exception */ public function update(): void { ... } }
Example 1. Create entity
$transactionManager->transactional(function () use ($transactionManager) { $transactionManager->persist(new Entity()); });
Example 2. Update entity using only TransactionManager
$transactionManager->transactional(function () use ($transactionManager, $entityId) { $entity = $transactionManager->getLocked(Entity::class, $entityId); $entity->update(); // if exception throws, transaction will be rolled back });
Example 3. Update entity using repository
$transactionManager->transactional(function () use ($transactionManager, $entityRepository, $criteria) { $entity = $entityRepository->getLocked($criteria); $entity->update(); // if exception throws, transaction will be rolled back $transactionManager->save($entity); });
Example 4. Nested transactions support
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); }
License
See LICENSE file.