aeatech/transaction-manager-bundle
Symfony bundle integrating AEATech Transaction Manager with support for multiple managers per connection, Doctrine DBAL adapter, MySQL/PostgreSQL transaction factories, configurable retry policies (backoff/jitter), attribute-based autoconfiguration, and lazy-loaded managers via service locator.
Install the Bundle
composer require aeatech/transaction-manager-bundle aeatech/transaction-manager-doctrine-adapter
Ensure aeatech/transaction-manager-doctrine-adapter is included for Doctrine DBAL support.
Enable the Bundle
Add to config/bundles.php:
return [
// ...
AeaTech\TransactionManagerBundle\AeaTechTransactionManagerBundle::class => ['all' => true],
];
Configure Basic Setup
Define a transaction manager in config/packages/aeatech_transaction_manager.yaml:
aeatech_transaction_manager:
managers:
default:
connection: default
adapter: doctrine_dbal
retry_policy: exponential_backoff
First Use Case: Wrap a Repository Call
use AeaTech\TransactionManager\TransactionManagerInterface;
class SomeService {
public function __construct(
private TransactionManagerInterface $transactionManager
) {}
public function updateUserData(): void {
$this->transactionManager->execute(function () {
// Your DB operations here (e.g., via Doctrine EntityManager)
$entityManager->persist($user);
$entityManager->flush();
});
}
}
Transaction Isolation
Use named managers for different database connections (e.g., default, replica):
aeatech_transaction_manager:
managers:
primary:
connection: default
adapter: doctrine_dbal
read_only:
connection: read_replica
adapter: doctrine_dbal
read_only: true
Access via:
$this->transactionManager->getManager('primary')->execute(...);
Retry Policies Customize retry behavior in config:
retry_policy:
type: exponential_backoff
max_attempts: 5
initial_interval: 100 # ms
multiplier: 2
jitter: true
Override globally or per-manager.
Attribute-Based Autoconfiguration Annotate services for automatic classification (e.g., for retry logic):
#[TransactionManager\ClassifyAsRetryable]
class PaymentService { ... }
Lazy-Loaded Managers Managers are initialized only when first accessed, reducing boot time:
$manager = $this->transactionManager->getManager('lazy_manager');
Doctrine EntityManager Integration
Pass the EntityManager to the transaction block:
$this->transactionManager->execute(function (EntityManager $em) {
$em->createQuery(...)->execute();
});
Symfony Messenger Integration
Use the TransactionManagerInterface as a middleware for async messages:
#[AsMessageHandler]
public function __invoke(PaymentMessage $message, TransactionManagerInterface $tm) {
$tm->execute(fn() => $this->processPayment($message));
}
Event Listeners Attach listeners to transaction lifecycle events:
$transactionManager->getManager('default')->addListener(
TransactionEvent::PRE_COMMIT,
fn(TransactionEvent $event) => $this->logTransactionStart($event)
);
Connection Mismatch Errors
Ensure the connection key in config matches your Doctrine DBAL connection names (e.g., default, read_replica). Verify with:
$this->connection->getDatabasePlatform()->getName();
Attribute Autoconfiguration Overrides
PHP attributes (e.g., @ClassifyAsRetryable) may conflict with explicit config. Precedence:
Lazy Loading Pitfalls Managers are not initialized until first access. Avoid circular dependencies where a service expects a manager to be pre-configured.
Retry Policy Misconfigurations
Exponential backoff with jitter: true may cause unexpected delays. Test with:
retry_policy:
type: fixed_interval
interval: 500 # ms (for testing)
Enable Verbose Logging
Add to config/packages/monolog.yaml:
handlers:
transaction:
type: stream
path: "%kernel.logs_dir%/transaction.log"
level: debug
channels: ["transaction"]
Then configure the bundle to use the transaction channel.
Check Transaction State
Use the TransactionManagerInterface to inspect active transactions:
$transaction = $this->transactionManager->getCurrentTransaction();
var_dump($transaction->isActive());
Custom Adapters
Implement AeaTech\TransactionManager\Adapter\AdapterInterface for non-Doctrine DBAL connections (e.g., Redis, Elasticsearch).
Dynamic Manager Creation
Extend the TransactionManagerRegistry to create managers on-the-fly:
$registry->setManagerFactory('dynamic', fn() => new CustomManager());
Custom Event Handlers
Extend TransactionEvent or create subclasses for domain-specific events:
class PaymentTransactionEvent extends TransactionEvent { ... }
Override Default Factories Replace the default factory for a connection type (e.g., MySQL) in config:
factories:
mysql: App\Custom\MySqlTransactionFactory
How can I help you explore Laravel packages today?