Skip to content

Commit

Permalink
Merge pull request doctrine#4622 from Warxcell/add_transaction_events
Browse files Browse the repository at this point in the history
Add events for Transaction begin/commit/rollBack.
  • Loading branch information
morozov authored May 23, 2021
2 parents 7d22dd9 + a85d913 commit 3cd4688
Showing 8 changed files with 279 additions and 0 deletions.
81 changes: 81 additions & 0 deletions docs/en/reference/events.rst
Original file line number Diff line number Diff line change
@@ -334,3 +334,84 @@ for event listeners.
It allows you to access the table index definitions of the current database, table name, Platform and
``Doctrine\DBAL\Connection`` instance. Indexes, that are about to be added, are not listed.

OnTransactionBegin Event
^^^^^^^^^^^^^^^^^^^^^^^^

``Doctrine\DBAL\Events::onTransactionBegin`` is triggered when ``Doctrine\DBAL\Connection::beginTransaction()``
is called. An instance of ``Doctrine\DBAL\Event\TransactionBeginEventArgs`` is injected as argument for event listeners.

.. code-block:: php
<?php
class MyEventListener
{
public function onTransactionBegin(TransactionBeginEventArgs $event)
{
// Your EventListener code
}
}
$evm = new EventManager();
$evm->addEventListener(Events::onTransactionBegin, new MyEventListener());
$conn = DriverManager::getConnection($connectionParams, null, $evm);
It allows you to access the ``Doctrine\DBAL\Connection`` instance.
Please note that this event can be called multiple times, since transactions can be nested.

OnTransactionCommit Event
^^^^^^^^^^^^^^^^^^^^^^^^^

``Doctrine\DBAL\Events::onTransactionCommit`` is triggered when ``Doctrine\DBAL\Connection::commit()`` is called.
An instance of ``Doctrine\DBAL\Event\TransactionCommitEventArgs`` is injected as argument for event listeners.

.. code-block:: php
<?php
class MyEventListener
{
public function onTransactionCommit(TransactionCommitEventArgs $event)
{
// Your EventListener code
}
}
$evm = new EventManager();
$evm->addEventListener(Events::onTransactionCommit, new MyEventListener());
$conn = DriverManager::getConnection($connectionParams, null, $evm);
It allows you to access the ``Doctrine\DBAL\Connection`` instance.
Please note that this event can be called multiple times, since transactions can be nested.
If you want to know if a transaction is actually committed, you should rely on
``TransactionCommitEventArgs::getConnection()->getTransactionNestingLevel() === 0`` or
``TransactionCommitEventArgs::getConnection()->isTransactionActive()``

OnTransactionRollBack Event
^^^^^^^^^^^^^^^^^^^^^^^^^^^

``Doctrine\DBAL\Events::onTransactionRollBack`` is triggered when ``Doctrine\DBAL\Connection::rollBack()`` is called.
An instance of ``Doctrine\DBAL\Event\TransactionRollBackEventArgs`` is injected as argument for event listeners.

.. code-block:: php
<?php
class MyEventListener
{
public function onTransactionRollBack(TransactionRollBackEventArgs $event)
{
// Your EventListener code
}
}
$evm = new EventManager();
$evm->addEventListener(Events::onTransactionRollBack, new MyEventListener());
$conn = DriverManager::getConnection($connectionParams, null, $evm);
It allows you to access the ``Doctrine\DBAL\Connection`` instance.
Please note that this event can be called multiple times, since transactions can be nested.
If you want to know if a transaction is actually rolled back, you should rely on
``TransactionCommitRollBackArgs::getConnection()->getTransactionNestingLevel() === 0`` or
``TransactionCommitRollBackArgs::getConnection()->isTransactionActive()``
9 changes: 9 additions & 0 deletions src/Connection.php
Original file line number Diff line number Diff line change
@@ -12,6 +12,9 @@
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Event\TransactionBeginEventArgs;
use Doctrine\DBAL\Event\TransactionCommitEventArgs;
use Doctrine\DBAL\Event\TransactionRollBackEventArgs;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\InvalidArgumentException;
@@ -1321,6 +1324,8 @@ public function beginTransaction()
}
}

$this->getEventManager()->dispatchEvent(Events::onTransactionBegin, new TransactionBeginEventArgs($this));

return true;
}

@@ -1368,6 +1373,8 @@ public function commit()

--$this->transactionNestingLevel;

$this->getEventManager()->dispatchEvent(Events::onTransactionCommit, new TransactionCommitEventArgs($this));

if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
return $result;
}
@@ -1444,6 +1451,8 @@ public function rollBack()
--$this->transactionNestingLevel;
}

$this->getEventManager()->dispatchEvent(Events::onTransactionRollBack, new TransactionRollBackEventArgs($this));

return true;
}

9 changes: 9 additions & 0 deletions src/Event/TransactionBeginEventArgs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Event;

class TransactionBeginEventArgs extends TransactionEventArgs
{
}
9 changes: 9 additions & 0 deletions src/Event/TransactionCommitEventArgs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Event;

class TransactionCommitEventArgs extends TransactionEventArgs
{
}
24 changes: 24 additions & 0 deletions src/Event/TransactionEventArgs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Event;

use Doctrine\Common\EventArgs;
use Doctrine\DBAL\Connection;

abstract class TransactionEventArgs extends EventArgs
{
/** @var Connection */
private $connection;

public function __construct(Connection $connection)
{
$this->connection = $connection;
}

public function getConnection(): Connection
{
return $this->connection;
}
}
9 changes: 9 additions & 0 deletions src/Event/TransactionRollBackEventArgs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Event;

class TransactionRollBackEventArgs extends TransactionEventArgs
{
}
3 changes: 3 additions & 0 deletions src/Events.php
Original file line number Diff line number Diff line change
@@ -30,4 +30,7 @@ private function __construct()
public const onSchemaAlterTableRenameColumn = 'onSchemaAlterTableRenameColumn';
public const onSchemaColumnDefinition = 'onSchemaColumnDefinition';
public const onSchemaIndexDefinition = 'onSchemaIndexDefinition';
public const onTransactionBegin = 'onTransactionBegin';
public const onTransactionCommit = 'onTransactionCommit';
public const onTransactionRollBack = 'onTransactionRollBack';
}
135 changes: 135 additions & 0 deletions tests/ConnectionTest.php
Original file line number Diff line number Diff line change
@@ -11,6 +11,9 @@
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Event\TransactionBeginEventArgs;
use Doctrine\DBAL\Event\TransactionCommitEventArgs;
use Doctrine\DBAL\Event\TransactionRollBackEventArgs;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\InvalidArgumentException;
@@ -135,6 +138,123 @@ public function testConnectDispatchEvent(): void
$conn->connect();
}

public function testTransactionBeginDispatchEvent(): void
{
$eventManager = new EventManager();
$driverMock = $this->createMock(Driver::class);
$driverMock->expects(self::any())
->method('connect')
->will(self::returnValue(
$this->createMock(DriverConnection::class)
));
$conn = new Connection([], $driverMock, new Configuration(), $eventManager);
$listenerMock = $this->createMock(TransactionBeginDispatchEventListener::class);
$listenerMock
->expects(self::exactly(1))
->method('onTransactionBegin')
->with(
self::callback(
static function (TransactionBeginEventArgs $eventArgs) use ($conn): bool {
return $eventArgs->getConnection() === $conn;
}
)
);
$eventManager->addEventListener([Events::onTransactionBegin], $listenerMock);

$conn->beginTransaction();
}

public function testTransactionCommitDispatchEvent(): void
{
$eventManager = new EventManager();
$driverMock = $this->createMock(Driver::class);
$driverMock->expects(self::any())
->method('connect')
->will(self::returnValue(
$this->createMock(DriverConnection::class)
));
$conn = new Connection([], $driverMock, new Configuration(), $eventManager);
$listenerMock = $this->createMock(TransactionCommitDispatchEventListener::class);
$listenerMock
->expects(self::exactly(1))
->method('onTransactionCommit')
->with(
self::callback(
static function (TransactionCommitEventArgs $eventArgs) use ($conn): bool {
return $eventArgs->getConnection() === $conn;
}
)
);
$eventManager->addEventListener([Events::onTransactionCommit], $listenerMock);

$conn->beginTransaction();
$conn->commit();
}

public function testTransactionCommitEventNotCalledAfterRollBack(): void
{
$eventManager = new EventManager();
$driverMock = $this->createMock(Driver::class);
$driverMock->expects(self::any())
->method('connect')
->will(self::returnValue(
$this->createMock(DriverConnection::class)
));
$conn = new Connection([], $driverMock, new Configuration(), $eventManager);
$rollBackListenerMock = $this->createMock(TransactionRollBackDispatchEventListener::class);
$rollBackListenerMock
->expects(self::exactly(1))
->method('onTransactionRollBack')
->with(
self::callback(
static function (TransactionRollBackEventArgs $eventArgs) use ($conn): bool {
return $eventArgs->getConnection() === $conn;
}
)
);
$eventManager->addEventListener([Events::onTransactionRollBack], $rollBackListenerMock);

$commitListenerMock = $this->createMock(TransactionCommitDispatchEventListener::class);
$commitListenerMock->expects(self::never())->method('onTransactionCommit');
$eventManager->addEventListener([Events::onTransactionCommit], $commitListenerMock);

$conn->beginTransaction();
$conn->beginTransaction();
$conn->rollBack();
try {
$conn->commit();
} catch (ConnectionException $exception) {
}
}

public function testTransactionRollBackDispatchEvent(): void
{
$eventManager = new EventManager();
$driverMock = $this->createMock(Driver::class);
$driverMock->expects(self::any())
->method('connect')
->will(self::returnValue(
$this->createMock(DriverConnection::class)
));
$conn = new Connection([], $driverMock, new Configuration(), $eventManager);
$listenerMock = $this->createMock(TransactionRollBackDispatchEventListener::class);
$listenerMock
->expects(self::exactly(1))
->method('onTransactionRollBack')
->with(
self::callback(
static function (TransactionRollBackEventArgs $eventArgs) use ($conn): bool {
return $eventArgs->getConnection() === $conn;
}
)
);

$eventManager->addEventListener([Events::onTransactionRollBack], $listenerMock);

$conn->beginTransaction();
$conn->rollBack();
}

public function testEventManagerPassedToPlatform(): void
{
$eventManager = new EventManager();
@@ -786,3 +906,18 @@ interface ConnectDispatchEventListener
{
public function postConnect(): void;
}

interface TransactionBeginDispatchEventListener
{
public function onTransactionBegin(): void;
}

interface TransactionCommitDispatchEventListener
{
public function onTransactionCommit(): void;
}

interface TransactionRollBackDispatchEventListener
{
public function onTransactionRollBack(): void;
}

0 comments on commit 3cd4688

Please sign in to comment.