Skip to content

Commit

Permalink
Add transaction handling
Browse files Browse the repository at this point in the history
  • Loading branch information
codeliner committed Jan 16, 2014
1 parent a4a7596 commit 9a16551
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 3 deletions.
13 changes: 12 additions & 1 deletion src/Malocher/EventStore/Adapter/AdapterException.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,22 @@ class AdapterException extends \Exception
/**
* Throw a configuration exception
*
* @param $msg
* @param string $msg
* @return AdapterException
*/
public static function configurationException($msg)
{
return new self('[Adapter Configuration Error] ' . $msg . "\n");
}

/**
* Throw an unsupported feature exception
*
* @param string $msg
* @return AdapterException
*/
public static function unsupportedFeatureException($msg)
{
return new self('[Adapter unsupported Feature] ' . $msg . "\n");
}
}
18 changes: 17 additions & 1 deletion src/Malocher/EventStore/Adapter/Doctrine/DoctrineDbalAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Schema\Schema;
use Malocher\EventStore\Adapter\AdapterInterface;
use Malocher\EventStore\Adapter\Feature\TransactionFeatureInterface;
use Malocher\EventStore\Adapter\AdapterException;
use Malocher\EventStore\EventSourcing\EventInterface;
use Malocher\EventStore\EventSourcing\SnapshotEvent;
Expand All @@ -24,7 +25,7 @@
* @author Manfred Weber <crafics@php.net>
* @package Malocher\EventStore\Adapter\Doctrine
*/
class DoctrineDbalAdapter implements AdapterInterface
class DoctrineDbalAdapter implements AdapterInterface, TransactionFeatureInterface
{
/**
* Doctrine DBAL connection
Expand Down Expand Up @@ -262,6 +263,21 @@ public function getCurrentSnapshotVersion($sourceFQCN, $sourceId)

return 0;
}

public function beginTransaction()
{
$this->conn->beginTransaction();
}

public function commit()
{
$this->conn->commit();
}

public function rollback()
{
$this->conn->rollBack();
}

/**
* Insert an event
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/*
* This file is part of the malocher/event-store package.
* (c) Manfred Weber <crafics@php.net> and Alexander Miertsch <kontakt@codeliner.ws>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Malocher\EventStore\Adapter\Feature;

/**
* Interface TransactionFeatureInterface
*
* @author Alexander Miertsch <kontakt@codeliner.ws>
*/
interface TransactionFeatureInterface
{
public function beginTransaction();

public function commit();

public function rollback();
}
86 changes: 85 additions & 1 deletion src/Malocher/EventStore/EventStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
namespace Malocher\EventStore;

use Malocher\EventStore\Adapter\AdapterInterface;
use Malocher\EventStore\Adapter\Feature\TransactionFeatureInterface;
use Malocher\EventStore\Adapter\AdapterException;
use Malocher\EventStore\Configuration\Configuration;
use Malocher\EventStore\EventSourcing\EventSourcedInterface;
use Malocher\EventStore\EventSourcing\EventSourcedObjectFactory;
Expand Down Expand Up @@ -76,6 +78,16 @@ class EventStore
* @var EventDispatcherInterface
*/
protected $eventDispatcher;

/**
* @var boolean
*/
protected $inTransaction = false;

/**
* @var array
*/
protected $pendingEvents = array();

/**
* Construct
Expand Down Expand Up @@ -180,7 +192,8 @@ public function save(EventSourcedInterface $eventSourcedObject)
);
}

$this->events()->dispatch(PostPersistEvent::NAME, $postPersistEvent);
$this->addPendingEvent($postPersistEvent);
$this->tryDispatchPostPersistEvents();
}

$hash = $this->getIdentityHash(
Expand Down Expand Up @@ -232,6 +245,53 @@ public function clear()
$this->identityMap = array();
}

/**
* Begin transaction
*
* @throws AdapterException If adapter does not support transactions
*/
public function beginTransaction()
{
if (!$this->adapter instanceof TransactionFeatureInterface) {
throw AdapterException::unsupportedFeatureException('TransactionFeature');
}

$this->inTransaction = true;
$this->adapter->beginTransaction();
}

/**
* Commit transaction
*
* @throws AdapterException If adapter does not support transactions
*/
public function commit()
{
if (!$this->adapter instanceof TransactionFeatureInterface) {
throw AdapterException::unsupportedFeatureException('TransactionFeature');
}

$this->adapter->commit();
$this->inTransaction = false;
$this->tryDispatchPostPersistEvents();
}

/**
* Rollback transaction
*
* @throws AdapterException If adapter does not support transactions
*/
public function rollback()
{
if (!$this->adapter instanceof TransactionFeatureInterface) {
throw AdapterException::unsupportedFeatureException('TransactionFeature');
}

$this->adapter->rollback();
$this->inTransaction = false;
$this->pendingEvents = array();
}

/**
* Get hash to identify EventSourcedObject in the IdentityMap
*
Expand All @@ -244,4 +304,28 @@ protected function getIdentityHash($sourceFQCN, $sourceId)
{
return $sourceFQCN . '::' . $sourceId;
}

/**
* @param PostPersistEvent $event
*/
protected function addPendingEvent(PostPersistEvent $event)
{
$this->pendingEvents[] = $event;
}


/**
* Events are only dispatched if the event store has no running transaction
*/
protected function tryDispatchPostPersistEvents()
{
if (!$this->inTransaction) {
$events = $this->pendingEvents;
$this->pendingEvents = array();

foreach ($events as $event) {
$this->events()->dispatch(PostPersistEvent::NAME, $event);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,65 @@ function(PostPersistEvent $e) use (&$persistedEventList) {

$this->assertEquals($check, $persistedEventList);
}

public function testBeginTransactionAndCommit()
{
$this->eventStore->beginTransaction();

$factory = new EventSourcedObjectFactory();
$user = $factory->create('Malocher\EventStoreTest\Coverage\Mock\User', '1');

$user->changeName('Malocher');
$user->changeEmail('my.email@getmalocher.org');

$persistedEventList = array();

$this->eventStore->events()->addListener(
PostPersistEvent::NAME,
function(PostPersistEvent $e) use (&$persistedEventList) {
foreach ($e->getPersistedEvents() as $persistedEvent) {
$persistedEventList[] = get_class($persistedEvent);
}
}
);

$this->eventStore->save($user);

$this->eventStore->commit();

$check = array(
'Malocher\EventStoreTest\Coverage\Mock\Event\UserNameChangedEvent',
'Malocher\EventStoreTest\Coverage\Mock\Event\UserEmailChangedEvent'
);

$this->assertEquals($check, $persistedEventList);
}

public function testBeginTransactionAndRollback()
{
$this->eventStore->beginTransaction();

$factory = new EventSourcedObjectFactory();
$user = $factory->create('Malocher\EventStoreTest\Coverage\Mock\User', '1');

$user->changeName('Malocher');
$user->changeEmail('my.email@getmalocher.org');

$persistedEventList = array();

$this->eventStore->events()->addListener(
PostPersistEvent::NAME,
function(PostPersistEvent $e) use (&$persistedEventList) {
foreach ($e->getPersistedEvents() as $persistedEvent) {
$persistedEventList[] = get_class($persistedEvent);
}
}
);

$this->eventStore->save($user);

$this->eventStore->rollback();

$this->assertEmpty($persistedEventList);
}
}

0 comments on commit 9a16551

Please sign in to comment.