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.
Symfony bundle that integrates AEATech Transaction Manager into the Symfony ecosystem.
aeatech/transaction-manager-doctrine-adapter.composer require aeatech/transaction-manager-bundle
Depending on your database, you also need to install the corresponding driver package:
composer require aeatech/transaction-manager-mysql
composer require aeatech/transaction-manager-postgresql
composer require aeatech/transaction-manager-doctrine-adapter
The bundle allows you to configure multiple transaction managers, each with its own platform and connection.
# config/packages/aea_tech_transaction_manager.yaml
aea_tech_transaction_manager:
default_manager: main
managers:
main:
platform: mysql
connection_adapter:
doctrine:
connection: default # Connection name from doctrine.yaml
retry_policy:
max_retries: 5 # Default: 7
backoff:
base_delay_ms: 100 # Default: 150
max_delay_ms: 5000 # Default: 2000
multiplier: 2.0 # Default: 2.0
jitter_ms: 100 # Default: 250
analytics:
platform: postgres
connection_adapter: 'App\PostgresCustomConnectionAdapter'
execution_plan_builder:
deferred_build_resolver:
strategy: caching # Options: "caching" (default) or "reflection"
error_classifier:
heuristics: 'App\PostgresCustomHeuristics'
retry_policy:
max_retries: 3
backoff: 'App\PostgresCustomBackoff'
doctrine: Requires aeatech/transaction-manager-doctrine-adapter. Specify the connection name.service: Provide a service ID implementing ConnectionInterface.deferred_build_resolver.strategy: How to detect transactions that need deferred building.deferred_build_resolver.service: Custom service ID implementing DeferredBuildResolverInterface.service: Custom service ID implementing ErrorClassifierInterface.heuristics: Custom service ID implementing DatabaseErrorHeuristicsInterface.The bundle provides PHP attributes to configure certain services automatically without modifying the YAML configuration. This is the recommended way for most use cases.
Note: If you configure a component (like heuristics or a classifier) for a manager both in YAML and via an attribute, an
InvalidConfigurationExceptionwill be thrown.
All attributes accept a managerNames parameter (string or array of strings).
| Attribute | Interface | Description |
|---|---|---|
#[AsHeuristics] |
DatabaseErrorHeuristicsInterface |
Overrides platform-specific error heuristics. |
#[AsErrorClassifier] |
ErrorClassifierInterface |
Provides custom error classification logic. |
#[AsDeferredBuildResolver] |
DeferredBuildResolverInterface |
Customizes how deferred builds are detected. |
#[AsConnectionAdapter] |
ConnectionInterface |
Provides a custom database connection adapter. |
#[AsRetryPolicyBackoff] |
BackoffStrategyInterface |
Implements a custom retry backoff strategy. |
Example:
use AEATech\TransactionManager\DatabaseErrorHeuristicsInterface;
use AEATech\TransactionManagerBundle\Attribute\AsHeuristics;
#[AsHeuristics(managerNames: 'main')]
class MyCustomHeuristics implements DatabaseErrorHeuristicsInterface
{
public function isConnectionIssue(?string $sqlState, ?int $driverCode, string $message): bool
{
// your logic
}
public function isTransientIssue(?string $sqlState, ?int $driverCode, string $message): bool
{
// your logic
}
}
The main way to interact with the bundle is through the TransactionManagerInterface and platform-specific factories.
use AEATech\TransactionManager\TransactionManagerInterface;
use AEATech\TransactionManager\MySQL\MySQLTransactionsFactoryInterface;
class RegisterUserService
{
public function __construct(
private TransactionManagerInterface $tm,
private MySQLTransactionsFactoryInterface $factory
) {}
public function register(string $email): void
{
// Create a transaction (this doesn't execute anything yet)
$tx = $this->factory->createInsert('users', ['email' => $email]);
// Run the transaction (includes retries on transient errors)
$this->tm->run($tx);
}
}
public function __construct(TransactionManagerInterface $tm)
{
// Injects the manager configured in 'default_manager'
}
use Symfony\Component\DependencyInjection\Attribute\Autowire;
public function __construct(
#[Autowire(service: 'aea_tech_transaction_manager.analytics')]
TransactionManagerInterface $analyticsTm
) {}
The bundle automatically creates aliases for platform-specific transaction factories for the default manager's platform.
use AEATech\TransactionManager\MySQL\MySQLTransactionsFactoryInterface;
public function __construct(MySQLTransactionsFactoryInterface $factory) { /* ... */ }
If you need to select a manager at runtime (e.g., in a worker or for multi-tenant applications), use TransactionManagerRegistryInterface.
use AEATech\TransactionManagerBundle\TransactionManagerRegistryInterface;
class MaintenanceCommand
{
public function __construct(private TransactionManagerRegistryInterface $registry) {}
public function execute(): void
{
// Get a specific manager by name
$tm = $this->registry->getManager('analytics');
// Get all registered manager names
$names = $this->registry->getManagerNames();
// Get the default manager name
$default = $this->registry->getDefaultManagerName();
}
}
When writing unit tests for your services, you should mock TransactionManagerInterface.
Using Mockery:
public function testRegister(): void
{
$tm = \Mockery::mock(TransactionManagerInterface::class);
$factory = \Mockery::mock(MySQLTransactionsFactoryInterface::class);
$tx = \Mockery::mock(\AEATech\TransactionManager\TransactionInterface::class);
$factory->shouldReceive('createInsert')->andReturn($tx);
// Expect the transaction to be run
$tm->shouldReceive('run')->with($tx)->once();
$service = new RegisterUserService($tm, $factory);
$service->register('test@example.com');
}
docker-compose -p aeatech-transaction-manager-bundle -f docker/docker-compose.yml up -d --build
docker-compose -p aeatech-transaction-manager-bundle -f docker/docker-compose.yml exec php-cli-8.2 composer install
# Run tests for all supported PHP versions
for v in 8.2 8.3 8.4; do \
echo "Testing PHP $v..."; \
docker-compose -p aeatech-transaction-manager-bundle -f docker/docker-compose.yml exec php-cli-$v vendor/bin/phpunit || break; \
done
# PHP 8.4
docker-compose -p aeatech-transaction-manager-bundle -f docker/docker-compose.yml exec php-cli-8.4 vendor/bin/phpunit
docker-compose -p aeatech-transaction-manager-bundle -f docker/docker-compose.yml exec php-cli-8.4 vendor/bin/phpstan analyse -c phpstan.neon --memory-limit=1G
docker-compose -p aeatech-transaction-manager-bundle -f docker/docker-compose.yml down -v
This project is licensed under the MIT License. See the LICENSE file for details.
How can I help you explore Laravel packages today?