Installation:
composer require axsy/transactional-bundle:dev-master
Replace dev-master with the latest stable version (e.g., 1.0.*).
Register the Bundle:
Add Axsy\TransactionalBundle\AxsyTransactionalBundle to config/bundles.php (Symfony 4+) or AppKernel.php (Symfony 2/3):
// config/bundles.php
return [
// ...
Axsy\TransactionalBundle\AxsyTransactionalBundle::class => ['all' => true],
// ...
];
Ensure JMS\AopBundle\JMSAopBundle and JMS\DiExtraBundle\JMSDiExtraBundle are also registered (included by default in Symfony Standard Edition).
Configure Defaults (optional):
Add to config/packages/axsy_transactional.yaml:
axsy_transactional:
default_connection: default
default_isolation: read_committed
First Use Case: Annotate a controller action or service method:
use Axsy\TransactionalBundle\Annotation\Transactionable;
class SomeController extends AbstractController
{
/**
* @Transactionable
*/
public function createAction()
{
// Database operations here
throw new \RuntimeException('Oops!');
// Transaction rolls back automatically
}
}
Controller-Level Transactions:
Use @Transactionable on controller methods to wrap HTTP requests in transactions. Ideal for:
POST /orders).PATCH /users/{id})./**
* @Transactionable(noRollbackFor={"Symfony\Component\HttpKernel\Exception\NotFoundHttpException"})
*/
public function updateUser(Request $request, User $user)
{
$user->update($request->request->all());
// Auto-rollback on exceptions except NotFoundHttpException
}
Service-Level Transactions: Annotate service methods to ensure business logic runs transactionally. Example:
class OrderService
{
/**
* @Transactionable(connection="read_replica", isolation="repeatable_read")
*/
public function processPayment(Order $order, Payment $payment)
{
$order->charge($payment);
// Uses custom connection/isolation
}
}
Class-Level Annotations:
Apply @Transactionable at the class level to inherit settings across all methods:
/**
* @Transactionable(rollbackFor={"App\Exception\ValidationException"})
*/
class InvoiceService
{
public function generate() { /* ... */ }
public function send() { /* ... */ }
// Both methods rollback only on ValidationException
}
Overriding Settings: Override class-level annotations on specific methods:
/**
* @Transactionable(noRollbackFor={"Symfony\Component\HttpKernel\Exception\NotFoundHttpException"})
*/
class ProductService
{
public function updateStock() { /* ... */ }
/**
* @Transactionable(rollbackFor={"App\Exception\StockException"})
*/
public function applyDiscount() { /* ... */ }
}
$this->get('event_dispatcher')->dispatch(
new TransactionEvent($this, TransactionEvent::PRE_COMMIT),
TransactionEvent::PRE_COMMIT
);
Doctrine\DBAL\ConnectionException or using Doctrine\DBAL\DriverManager with an in-memory database (e.g., SQLite).Annotation Parsing:
JMS\AopBundle is misconfigured or missing.JMSAopBundle is registered and jms.di_extra.autowire is enabled in config/packages/jms_di_extra.yaml:
jms_di_extra:
autowire: true
Connection Isolation:
serializable) may throw Doctrine\DBAL\Exception\InvalidArgumentException.read_uncommitted, read_committed, repeatable_read, or serializable.Nested Transactions:
@Transactionable method from another may cause unexpected rollbacks.Doctrine\DBAL\Connection::beginTransaction() manually for nested logic.Exception Handling:
RuntimeException) will trigger rollbacks, but silent errors (e.g., E_ERROR) may not.try-catch blocks or use noRollbackFor to exclude specific exceptions.Configuration Overrides:
Enable AOP Logging:
Add to config/packages/dev/jms_aop.yaml:
jms_aop:
proxy_validator: '%kernel.debug%'
logging: '%kernel.debug%'
Check logs for AxsyTransactionalBundle entries during transactions.
Doctrine Events:
Listen for postFlush/onFlush events to debug transaction boundaries:
$entityManager->getEventManager()->addEventListener(
\Doctrine\ORM\Events::postFlush,
function() { \Log::debug('Transaction flushed'); }
);
Custom Annotations:
Extend the bundle by creating a custom annotation processor. Implement Axsy\TransactionalBundle\Annotation\TransactionableInterface and register it via services.yaml:
services:
App\Annotation\CustomTransactionable:
tags:
- { name: 'axsy_transactional.annotation_processor' }
Transaction Listeners:
Subscribe to axsy_transactional.transaction.start/commit/rollback events:
$dispatcher->addListener(
'axsy_transactional.transaction.start',
function() { \Log::info('Transaction started'); }
);
Connection Management:
Dynamically switch connections by implementing Axsy\TransactionalBundle\Connection\ConnectionProviderInterface and binding it in services.yaml:
services:
App\Connection\DynamicConnectionProvider:
arguments: ['@database_connection']
tags: ['axsy_transactional.connection_provider']
How can I help you explore Laravel packages today?