From 058c19f5af3f7f8acbc58be86917d56030afda28 Mon Sep 17 00:00:00 2001 From: Doug Hurst Date: Thu, 23 Jun 2011 19:06:43 -0500 Subject: [PATCH] Managed to put everything into a more PEAR-like format, adding docblocks where necessary, all *without* replacing my name for Sean's. --- examples/example1/Employee.php | 81 +-- examples/example1/Employer.php | 153 +++--- examples/example1/Position.php | 41 +- examples/example1/Welcomer.php | 36 +- examples/example1/example1.php | 24 +- library/Tracks.php | 1 - library/Tracks/Event/Base.php | 73 ++- library/Tracks/EventHandler/DirectRouter.php | 121 +++-- library/Tracks/EventHandler/IEventHandler.php | 30 +- library/Tracks/EventHandler/IEventRouter.php | 30 +- .../Tracks/EventStore/EventStorage/Memory.php | 185 ++++--- library/Tracks/EventStore/IEventStore.php | 69 ++- library/Tracks/EventStore/ISnapshotStore.php | 47 +- library/Tracks/EventStore/Repository.php | 486 ++++++++++-------- .../EventStore/SnapshotStorage/File.php | 104 +++- .../EventStore/SnapshotStorage/Memory.php | 88 ++-- library/Tracks/Exception/Base.php | 27 +- .../Exception/HandlerAlreadyRegistered.php | 59 ++- library/Tracks/Model/AggregateRoot.php | 31 +- library/Tracks/Model/Entity.php | 443 +++++++++------- library/Tracks/Model/EntityList.php | 421 +++++++++------ library/Tracks/Model/Guid.php | 87 ++-- 22 files changed, 1657 insertions(+), 980 deletions(-) diff --git a/examples/example1/Employee.php b/examples/example1/Employee.php index f691763..ac6abfa 100644 --- a/examples/example1/Employee.php +++ b/examples/example1/Employee.php @@ -1,43 +1,64 @@ - * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @category Tracks + * @package Examples + * @subpackage Example1 + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ -class Employee extends \Tracks\Model\Entity { +/** + * Example Domain Entity + * + * @category Tracks + * @package Examples + * @subpackage Example1 + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class Employee extends \Tracks\Model\Entity +{ + public function __construct($guid, $name) + { + $this->guid = $guid; + $this->name = $name; + $this->registerEvents(); + } - public function __construct($guid, $name) { - $this->guid = $guid; - $this->name = $name; - $this->registerEvents(); - } + public function changeTitle($title) + { + $this->applyEvent(new EventEmployeeChangeTitle($this->getGuid(), $title)); + } - public function changeTitle($title) { - $this->applyEvent(new EventEmployeeChangeTitle($this->getGuid(), $title)); - } + public function onChangeTitle(EventEmployeeChangeTitle $event) + { + $this->position = new Position($event->title); + } - public function onChangeTitle(EventEmployeeChangeTitle $event) { - $this->position = new Position($event->title); - } - - private function registerEvents() { - $this->registerEvent('EventEmployeeChangeTitle', 'onChangeTitle'); - } + private function registerEvents() + { + $this->registerEvent('EventEmployeeChangeTitle', 'onChangeTitle'); + } - public $name; - public $position; + public $name; + public $position; } -class EventEmployeeChangeTitle extends \Tracks\Event\Base { - - public function __construct($guid, $title) { - parent::__construct($guid); - $this->title = $title; - } +class EventEmployeeChangeTitle extends \Tracks\Event\Base +{ + public function __construct($guid, $title) + { + parent::__construct($guid); + $this->title = $title; + } - public $title; + public $title; } diff --git a/examples/example1/Employer.php b/examples/example1/Employer.php index 08aeb79..a26cc02 100644 --- a/examples/example1/Employer.php +++ b/examples/example1/Employer.php @@ -1,77 +1,102 @@ - * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @category Tracks + * @package Examples + * @subpackage Example1 + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ -class Employer extends \Tracks\Model\AggregateRoot { - - public function __construct() { - $this->employees = new \Tracks\Model\EntityList; - $this->registerEvents(); - } - - public function create($name) { - $guid = \Tracks\Model\Guid::create(); - $this->applyEvent(new EventEmployerCreated($guid, $name)); - return $guid; - } - - public function addNewEmployee($name, $title) { - $employeeGuid = \Tracks\Model\Guid::create(); - $this->applyEvent(new EventEmployeeAdded($this->getGuid(), $employeeGuid, $name)); - $this->employees->find($employeeGuid)->changeTitle($title); - return $employeeGuid; - } - - public function changeEmployeeTitle(\Tracks\Model\Guid $employeeGuid, $title) { - if ($employee = $this->employees->find($employeeGuid)) { - $employee->changeTitle($title); - } - } - - protected function onEmployerCreated(EventEmployerCreated $event) { - $this->guid = $event->guid; - $this->name = $event->name; - } - - protected function onEmployeeAdded(EventEmployeeAdded $event) { - $this->employees->add(new Employee($event->employeeGuid, $event->name)); - } - - private function registerEvents() { - $this->registerEvent('EventEmployerCreated', 'onEmployerCreated'); - $this->registerEvent('EventEmployeeAdded', 'onEmployeeAdded'); - } - - public $name; - public $employees; +/** + * Example Domain Aggregate Root + * + * @category Tracks + * @package Examples + * @subpackage Example1 + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class Employer extends \Tracks\Model\AggregateRoot +{ + public function __construct() + { + $this->employees = new \Tracks\Model\EntityList; + $this->registerEvents(); + } + + public function create($name) + { + $guid = \Tracks\Model\Guid::create(); + $this->applyEvent(new EventEmployerCreated($guid, $name)); + return $guid; + } + + public function addNewEmployee($name, $title) + { + $employeeGuid = \Tracks\Model\Guid::create(); + $this->applyEvent(new EventEmployeeAdded($this->getGuid(), $employeeGuid, $name)); + $this->employees->find($employeeGuid)->changeTitle($title); + return $employeeGuid; + } + + public function changeEmployeeTitle(\Tracks\Model\Guid $employeeGuid, $title) + { + if ($employee = $this->employees->find($employeeGuid)) { + $employee->changeTitle($title); + } + } + + protected function onEmployerCreated(EventEmployerCreated $event) + { + $this->guid = $event->guid; + $this->name = $event->name; + } + + protected function onEmployeeAdded(EventEmployeeAdded $event) + { + $this->employees->add(new Employee($event->employeeGuid, $event->name)); + } + + private function registerEvents() + { + $this->registerEvent('EventEmployerCreated', 'onEmployerCreated'); + $this->registerEvent('EventEmployeeAdded', 'onEmployeeAdded'); + } + + public $name; + public $employees; } -class EventEmployerCreated extends \Tracks\Event\Base { - - public function __construct($guid, $name) { - parent::__construct($guid); - $this->name = $name; - } +class EventEmployerCreated extends \Tracks\Event\Base +{ + public function __construct($guid, $name) + { + parent::__construct($guid); + $this->name = $name; + } - public $name; + public $name; } -class EventEmployeeAdded extends \Tracks\Event\Base { - - public function __construct(\Tracks\Model\Guid $guid, $employeeGuid, $name) { - parent::__construct($guid); - $this->employeeGuid = $employeeGuid; - $this->name = $name; - } - - public $employeeGuid; - public $name; +class EventEmployeeAdded extends \Tracks\Event\Base +{ + public function __construct(\Tracks\Model\Guid $guid, $employeeGuid, $name) + { + parent::__construct($guid); + $this->employeeGuid = $employeeGuid; + $this->name = $name; + } + + public $employeeGuid; + public $name; } diff --git a/examples/example1/Position.php b/examples/example1/Position.php index 974ce56..8934003 100644 --- a/examples/example1/Position.php +++ b/examples/example1/Position.php @@ -1,19 +1,36 @@ - * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @category Tracks + * @package Examples + * @subpackage Example1 + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ -class Position { - - public function __construct($title) { - $this->title = $title; - } +/** + * Example Domain Value Object + * + * @category Tracks + * @package Examples + * @subpackage Example1 + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class Position +{ + public function __construct($title) + { + $this->title = $title; + } - public $title; + public $title; } - + diff --git a/examples/example1/Welcomer.php b/examples/example1/Welcomer.php index 8b2fb61..e7bd425 100644 --- a/examples/example1/Welcomer.php +++ b/examples/example1/Welcomer.php @@ -1,16 +1,34 @@ - * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @category Tracks + * @package Examples + * @subpackage Example1 + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ -class Welcomer implements \Tracks\EventHandler\IEventHandler { +/** + * Example Event Handler + * + * @category Tracks + * @package Examples + * @subpackage Example1 + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class Welcomer implements \Tracks\EventHandler\IEventHandler +{ - public function execute(\Tracks\Event\Base $event) { - echo "Welcome aboard, {$event->name}!\n"; - } + public function execute(\Tracks\Event\Base $event) + { + echo "Welcome aboard, {$event->name}!\n"; + } } \ No newline at end of file diff --git a/examples/example1/example1.php b/examples/example1/example1.php index cb462e0..723556c 100644 --- a/examples/example1/example1.php +++ b/examples/example1/example1.php @@ -2,10 +2,15 @@ /** * Example 1 * - * @author Sean Crystal - * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * PHP Version 5.3 + * + * @category Tracks + * @package Examples + * @subpackage Example1 + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ require_once '../../library/Tracks.php'; @@ -18,9 +23,10 @@ $router->addHandler('EventEmployeeAdded', 'Welcomer'); $repository = new \Tracks\EventStore\Repository( - new \Tracks\EventStore\EventStorage\Memory, - $router, - new \Tracks\EventStore\SnapshotStorage\Memory); + new \Tracks\EventStore\EventStorage\Memory, + $router, + new \Tracks\EventStore\SnapshotStorage\Memory +); $employer = new Employer; $employerGuid = $employer->create('Planet Express'); @@ -35,8 +41,8 @@ $repository->save($employer); -echo PHP_EOL, $employer->name, PHP_EOL; +echo PHP_EOL.$employer->name.PHP_EOL; foreach ($employer->employees as $employee) { - echo " - {$employee->name}, {$employee->position->title}\n"; + echo " - {$employee->name}, {$employee->position->title}\n"; } diff --git a/library/Tracks.php b/library/Tracks.php index 6d3bf76..bbcdc40 100644 --- a/library/Tracks.php +++ b/library/Tracks.php @@ -12,7 +12,6 @@ * @link https://github.com/spiralout/Tracks */ - /** * Tracks library autoloader * diff --git a/library/Tracks/Event/Base.php b/library/Tracks/Event/Base.php index 345671e..146b06a 100644 --- a/library/Tracks/Event/Base.php +++ b/library/Tracks/Event/Base.php @@ -1,36 +1,55 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package Event + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\Event; use Tracks\Model\Guid; -abstract class Base { - - /** - * Constructor - * - * @param Guid $guid - */ - public function __construct(Guid $guid) { - $this->guid = $guid; - } - - /** - * Get the entity guid this event is associated with - * - * @return Tracks\Model\Guid - */ - public function getGuid() { - return $this->guid; - } - - /** @var \Tracks\Model\Guid */ - public $guid; +/** + * Domain Event base class + * + * @category Tracks + * @package Event + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +abstract class Base +{ + + /** + * Constructor + * + * @param Guid $guid Event GUID + * + * @return null + */ + public function __construct(Guid $guid) + { + $this->guid = $guid; + } + + /** + * Get the entity guid this event is associated with + * + * @return Tracks\Model\Guid + */ + public function getGuid() + { + return $this->guid; + } + + /** @var \Tracks\Model\Guid */ + public $guid; } diff --git a/library/Tracks/EventHandler/DirectRouter.php b/library/Tracks/EventHandler/DirectRouter.php index 0fb88df..ef93273 100644 --- a/library/Tracks/EventHandler/DirectRouter.php +++ b/library/Tracks/EventHandler/DirectRouter.php @@ -1,54 +1,83 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package EventHandler + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\EventHandler; -class DirectRouter implements IEventRouter { - - /** - * Route an event - * - * @param Tracks\Event\Base $event - */ - public function route(\Tracks\Event\Base $event) { - if (isset($this->handlers[get_class($event)])) { - foreach ($this->handlers[get_class($event)] as $handler) { - if (is_string($handler)) { - $handler = new $handler; - } - - $handler->execute($event); - } - } - } - - /** - * Add an event handler to the routing table - * The 2nd argument may be either an instantiated object, or the name of a class to instantiate - * In the second case, the class should not have any required parameters on it's constructor - * - * @param string $eventClass - * @param IEventHandler|string $handler - */ - public function addHandler($eventClass, $handler) { - if (is_object($handler) && !($handler instanceof \Tracks\EventHandler\IEventHandler)) { - throw new Exception('Event handlers must implement \Tracks\EventHandler\IEventHandler'); - } - - if (!isset($this->handlers[$eventClass])) { - $this->handlers[$eventClass] = array(); - } - - $this->handlers[$eventClass][] = $handler; - } - - /** @var array */ - private $handlers = array(); +/** + * Routes directly to an event handler object in the same process + * + * @category Tracks + * @package EventHandler + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ + + +class DirectRouter implements IEventRouter +{ + + /** + * Route an event + * + * @param Tracks\Event\Base $event An Event + * + * @return null + */ + public function route(\Tracks\Event\Base $event) + { + if (isset($this->_handlers[get_class($event)])) { + foreach ($this->_handlers[get_class($event)] as $handler) { + if (is_string($handler)) { + $handler = new $handler; + } + + $handler->execute($event); + } + } + } + + /** + * Add an event handler to the routing table + * + * The 2nd argument may be either an instantiated object, or the name of a + * class to instantiate. In the second case, the class should not have any + * required parameters on it's constructor. + * + * @param string $eventClass The Event classname + * @param IEventHandler|string $handler An EventHandler + * + * @return null + */ + public function addHandler($eventClass, $handler) + { + assert('is_string($eventClass)'); + assert('class_exists($eventClass)'); + if (is_object($handler) + && !($handler instanceof \Tracks\EventHandler\IEventHandler) + ) { + throw new LogicException('Event handlers must implement \Tracks\EventHandler\IEventHandler'); + } + + if (!isset($this->_handlers[$eventClass])) { + $this->_handlers[$eventClass] = array(); + } + + $this->_handlers[$eventClass][] = $handler; + } + + /** @var array */ + private $_handlers = array(); } diff --git a/library/Tracks/EventHandler/IEventHandler.php b/library/Tracks/EventHandler/IEventHandler.php index a2d6a82..d7e2710 100644 --- a/library/Tracks/EventHandler/IEventHandler.php +++ b/library/Tracks/EventHandler/IEventHandler.php @@ -1,16 +1,30 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package EventHandler + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\EventHandler; -interface IEventHandler { - - public function execute(\Tracks\Event\Base $event); +/** + * Interface for event handlers + * + * @category Tracks + * @package EventHandler + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +interface IEventHandler +{ + public function execute(\Tracks\Event\Base $event); } diff --git a/library/Tracks/EventHandler/IEventRouter.php b/library/Tracks/EventHandler/IEventRouter.php index a248992..68438aa 100644 --- a/library/Tracks/EventHandler/IEventRouter.php +++ b/library/Tracks/EventHandler/IEventRouter.php @@ -1,17 +1,31 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package EventHandler + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\EventHandler; -interface IEventRouter { - - public function route(\Tracks\Event\Base $event); +/** + * Interface for event routers + * + * @category Tracks + * @package EventHandler + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +interface IEventRouter +{ + public function route(\Tracks\Event\Base $event); public function addHandler($eventClass, $handler); } diff --git a/library/Tracks/EventStore/EventStorage/Memory.php b/library/Tracks/EventStore/EventStorage/Memory.php index c632afb..12dc7f4 100644 --- a/library/Tracks/EventStore/EventStorage/Memory.php +++ b/library/Tracks/EventStore/EventStorage/Memory.php @@ -1,98 +1,129 @@ - * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package EventStore + * @subpackage EventStorage + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\EventStore\EventStorage; use \Tracks\EventStore\IEventStore; use \Tracks\Model\Guid, \Tracks\Model\Entity; -class Memory implements IEventStore { +/** + * In-Memory event store implementation + * + * @category Tracks + * @package EventStore + * @subpackage EventStorage + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class Memory implements IEventStore +{ - /** - * Get all the events associated with an entity by guid - * - * @param Guid $guid - * @return array - */ - public function getAllEvents(Guid $guid) { - return $this->events[(string) $guid]; - } + /** + * Get all the events associated with an entity by guid + * + * @param Guid $guid An Entity's GUID + * + * @return array + */ + public function getAllEvents(Guid $guid) + { + return $this->_events[(string) $guid]; + } - /** - * Get all events associated with an entity starting from a particular version - * - * @param Guid $guid - * @param int $version - * @return array - */ - public function getEventsFromVersion(Guid $guid, $version) { - assert('is_int($version)'); - - return array_slice($this->events[(string) $guid], $version); - } + /** + * Get all events associated with an entity starting from a particular version + * + * @param Guid $guid An Entity's GUID + * @param int $version That Entity's version number + * + * @return array + */ + public function getEventsFromVersion(Guid $guid, $version) + { + assert('is_int($version)'); + return array_slice($this->_events[(string) $guid], $version); + } - /** - * Save an entity and it's events - * - * @param Entity $entity - */ - public function save(Entity $entity) { - foreach ($entity->getAllEntities() as $provider) { - $this->createEntity($provider); - } + /** + * Save an entity and it's events + * + * @param Entity $entity An Entity + * + * @return null + */ + public function save(Entity $entity) + { + foreach ($entity->getAllEntities() as $provider) { + $this->_createEntity($provider); + } - foreach ($entity->getAllAppliedEvents() as $event) { - $this->events[(string) $event->getGuid()][] = clone $event; - $this->incVersion($event->getGuid()); - } - } + foreach ($entity->getAllAppliedEvents() as $event) { + $this->events[(string) $event->getGuid()][] = clone $event; + $this->_incVersion($event->getGuid()); + } + } - /** - * Get the object type associated with a guid - * - * @param Guid $guid - * @return string - */ - public function getType(Guid $guid) { - return $this->entities[(string) $guid]['type']; - } + /** + * Get the object type associated with a guid + * + * @param Guid $guid Any GUID + * + * @return string + */ + public function getType(Guid $guid) + { + return $this->entities[(string) $guid]['type']; + } - /** - * Create a new entity entry if it doesn't already exist - * - * @param Entity $entity - */ - private function createEntity(Entity $entity) { - if (!isset($this->entities[(string) $entity->getGuid()])) { - $this->entities[(string) $entity->getGuid()] = array( + /** + * Create a new entity entry if it doesn't already exist + * + * @param Entity $entity An Entity + * + * @return null + */ + private function _createEntity(Entity $entity) + { + if (!isset($this->_entities[(string) $entity->getGuid()])) { + $this->_entities[(string) $entity->getGuid()] = array( 'type' => get_class($entity), 'version' => 0, 'guid' => $entity->getGuid()); - } + } + + if (!isset($this->_events[(string) $entity->getGuid()])) { + $this->_events[(string) $entity->getGuid()] = array(); + } + } - if (!isset($this->events[(string) $entity->getGuid()])) { - $this->events[(string) $entity->getGuid()] = array(); - } - } + /** + * Increment the version of an entity + * + * @param Guid $guid An Entity's GUID + * + * @return null + */ + private function _incVersion(Guid $guid) + { + $this->entities[(string) $guid]['version']++; + } - /** - * Increment the version of an entity - * - * @param Guid $guid - */ - private function incVersion(Guid $guid) { - $this->entities[(string) $guid]['version']++; - } + /** @var array */ + private $_events = array(); - /** @var array */ - private $events = array(); - - /** @var array */ - private $entities = array(); + /** @var array */ + private $_entities = array(); } diff --git a/library/Tracks/EventStore/IEventStore.php b/library/Tracks/EventStore/IEventStore.php index e470181..3ab8d74 100644 --- a/library/Tracks/EventStore/IEventStore.php +++ b/library/Tracks/EventStore/IEventStore.php @@ -1,20 +1,67 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package EventStore + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\EventStore; use \Tracks\Model\Guid, \Tracks\Model\Entity; -interface IEventStore { - - public function getAllEvents(Guid $guid); - public function getEventsFromVersion(Guid $guid, $version); - public function getType(Guid $guid); - public function save(Entity $entity); +/** + * Interface for event stores + * + * @category Tracks + * @package EventStore + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +interface IEventStore +{ + + /** + * Get all the events associated with an entity by guid + * + * @param Guid $guid An Entity's GUID + * + * @return array + */ + public function getAllEvents(Guid $guid); + + /** + * Get all events associated with an entity starting from a particular version + * + * @param Guid $guid An Entity's GUID + * @param int $version That Entity's version number + * + * @return array + */ + public function getEventsFromVersion(Guid $guid, $version); + + /** + * Get the object type associated with a guid + * + * @param Guid $guid Any GUID + * + * @return string + */ + public function getType(Guid $guid); + + /** + * Save an entity and it's events + * + * @param Entity $entity An Entity + * + * @return null + */ + public function save(Entity $entity); } diff --git a/library/Tracks/EventStore/ISnapshotStore.php b/library/Tracks/EventStore/ISnapshotStore.php index a8be772..baa329b 100644 --- a/library/Tracks/EventStore/ISnapshotStore.php +++ b/library/Tracks/EventStore/ISnapshotStore.php @@ -1,18 +1,47 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package EventStore + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\EventStore; use Tracks\Model\Guid, Tracks\Model\Entity; -interface ISnapshotStore { - - public function load(Guid $guid); - public function save(Entity $entity); +/** + * Interface for snapshot stores + * + * @category Tracks + * @package EventStore + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +interface ISnapshotStore +{ + /** + * Load an Entity + * + * @param Guid $guid An Entity's GUID + * + * @return Entity|null + */ + public function load(Guid $guid); + + /** + * Save an Entity + * + * @param Entity $entity An Entity + * + * @return null + */ + public function save(Entity $entity); } diff --git a/library/Tracks/EventStore/Repository.php b/library/Tracks/EventStore/Repository.php index 075f4b6..3eb5f42 100644 --- a/library/Tracks/EventStore/Repository.php +++ b/library/Tracks/EventStore/Repository.php @@ -1,222 +1,284 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package EventStore + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\EventStore; use Tracks\EventHandler\IEventRouter; use Tracks\Model\AggregateRoot, Tracks\Model\Entity, Tracks\Model\Guid; -class Repository { - - const SNAPSHOT_FREQUENCY = 100; - - /** - * Constructor - * - * @param IEventStore $eventStore - * @param IEventRouter $router - * @param ISnapshotStore $snapshotStore - */ - public function __construct(IEventStore $eventStore, IEventRouter $router, ISnapshotStore $snapshotStore) { - $this->eventStore = $eventStore; - $this->snapshotStore = $snapshotStore; - $this->router = $router; - $this->snapshotFrequency = self::SNAPSHOT_FREQUENCY; - } - - /** - * Load an entity by a guid - * - * @param Guid $guid - * @return \Tracks\Model\AggregateRoot - */ - public function load(Guid $guid) { - if (is_null($aggregateRoot = $this->loadEntity($guid))) { - return NULL; - } - - if (($children = $aggregateRoot->getAllChildEntities())) { - foreach ($children as &$child) { - $child = $this->loadEntity($child->getGuid(), $child); - } - } - - return $aggregateRoot; - } - - /** - * Set the frequency with which to take snapshots of entities - * - * @param int $numEvents - */ - public function setSnapshotFrequency($numEvents = self::SNAPSHOT_FREQUENCY) { - assert('$numEvents > 0'); - - $this->snapshotFrequency = $numEvents; - } - - /** - * Save an aggregate root, and call event handlers for all new events - * - * @param AggregateRoot $aggregateRoot - */ - public function save(AggregateRoot $aggregateRoot) { - if (count($aggregateRoot->getAllAppliedEvents()) == 0) { - return; - } - - $this->eventStore->save($aggregateRoot); - $this->routeEvents($aggregateRoot); - $this->updateVersionsAndClearEvents($aggregateRoot); - $this->saveSnapshots($aggregateRoot); - } - - /** - * Store an entity in the identity map - * - * @param Entity $entity - */ - private function storeInIdentityMap(Entity $entity) { - $this->identityMap[(string) $entity->getGuid()] = $entity; - } - - /** - * Attempt to load an entity from the identity map - * - * @param Guid $guid - * @return \Tracks\Model\Entity - */ - private function loadFromIdentityMap(Guid $guid) { - return (isset($this->identityMap[(string) $guid]) ? $this->identityMap[(string) $guid] : NULL); - } - - /** - * Load an entity from it's event history - * - * @param Guid $guid - * @param Entity $entity - * @return \Tracks\Model\Entity - */ - private function loadFromHistory(Guid $guid, Entity $entity = NULL) { - $events = $this->eventStore->getAllEvents($guid); - - if (is_null($entity)) { - if (is_null($modelClass = $this->eventStore->getType($guid))) { - return NULL; - } - - $entity = new $modelClass; - } - - $entity->loadHistory($events); - - return $entity; - } - - /** - * Load an entity into memory - * - * @param Guid $guid - * @param Entity $entity - * @return Tracks\Model\Entity - */ - private function loadEntity(Guid $guid, Entity $entity = NULL) { - $loadedEntity = $this->loadFromIdentityMap($guid); - - if (is_null($loadedEntity)) { - $loadedEntity = $this->loadFromSnapshot($guid); - } - - if (is_null($loadedEntity)) { - $loadedEntity = $this->loadFromHistory($guid, $entity); - } - - if (is_null($loadedEntity)) { - return NULL; - } - - $this->storeInIdentityMap($loadedEntity); - - return $loadedEntity; - } - - /** - * Route all new events on an aggregate to their appropriate handlers - * - * @param AggregateRoot $aggregateRoot - */ - private function routeEvents(AggregateRoot $aggregateRoot) { - foreach ($aggregateRoot->getAllAppliedEvents() as $event) { - $this->router->route($event); - } - } - - /** - * Attempt to load an entity from a snapshot - * - * @param Guid $guid - * @return \Tracks\Model\Entity - */ - private function loadFromSnapshot(Guid $guid) { - if (is_null($entity = $this->snapshotStore->load($guid))) { - return NULL; - } - - $entity->loadHistory($this->eventStore->getEventsFromVersion($entity->getGuid(), $entity->getVersion() + 1)); - - return $entity; - } - - /** - * Update an entity's in-memory version and clear applied events - * This should be called after an entity's events have been saved - */ - private function updateVersionsAndClearEvents(AggregateRoot $aggegrateRoot) { - foreach ($aggegrateRoot->getAllEntities() as $entity) { - $entity->incVersion(count($entity->getAppliedEvents())); - $entity->clearAppliedEvents(); - } - } - - /** - * Attempt to save a snapshot for all entites in an aggregate root - * - * @param AggregateRoot $aggregateRoot - */ - private function saveSnapshots(AggregateRoot $aggregateRoot) { - foreach ($aggregateRoot->getAllEntities() as $entity) { - $this->saveSnapshot($entity); - } - } - - /** - * Try to save a snapshot of an entity if frequency determines it is time - * - * @param Entity $entity - */ - private function saveSnapshot(Entity $entity) { - $state = clone $entity; - $state->clearAppliedEvents(); - - $snapshot = $this->snapshotStore->load($state->getGuid()); - - if ((!$snapshot && $state->getVersion() >= $this->snapshotFrequency) - || ($snapshot && ($state->getVersion() - $snapshot->getVersion()) >= $this->snapshotFrequency) - ) { - echo 'Saving snapshot', PHP_EOL; - $this->snapshotStore->save($entity); - } - } - - /** @var int */ - private $snapshotFrequency; - - /** @var array */ - private $identityMap = array(); +/** + * Repository to load and save domain event-based entities + * + * @category Tracks + * @package EventStore + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class Repository +{ + + const SNAPSHOT_FREQUENCY = 100; + + /** + * Constructor + * + * @param IEventStore $eventStore The Event Store + * @param IEventRouter $router The Event Router + * @param ISnapshotStore $snapshotStore The Snapshot Store + * + * @return null + */ + public function __construct( + IEventStore $eventStore, + IEventRouter $router, + ISnapshotStore $snapshotStore + ) { + $this->eventStore = $eventStore; + $this->snapshotStore = $snapshotStore; + $this->router = $router; + $this->snapshotFrequency = self::SNAPSHOT_FREQUENCY; + } + + /** + * Load an entity by a guid + * + * @param Guid $guid An Entity's GUID + * + * @return \Tracks\Model\AggregateRoot + */ + public function load(Guid $guid) + { + if (is_null($aggregateRoot = $this->_loadEntity($guid))) { + return null; + } + + if (($children = $aggregateRoot->getAllChildEntities())) { + foreach ($children as &$child) { + $child = $this->_loadEntity($child->getGuid(), $child); + } + } + + return $aggregateRoot; + } + + /** + * Set the frequency with which to take snapshots of entities + * + * @param int $numEvents The snapshot threshhold as a max number of events + * to allow before a snapshot happens + * + * @return null + */ + public function setSnapshotFrequency($numEvents = self::SNAPSHOT_FREQUENCY) + { + assert('is_int($numEvents)'); + assert('$numEvents > 0'); + $this->snapshotFrequency = $numEvents; + } + + /** + * Save an aggregate root, and call event handlers for all new events + * + * @param AggregateRoot $aggregateRoot An Aggregate Root + * + * @return null + */ + public function save(AggregateRoot $aggregateRoot) + { + if (count($aggregateRoot->getAllAppliedEvents()) == 0) { + return; + } + + $this->eventStore->save($aggregateRoot); + $this->_routeEvents($aggregateRoot); + $this->_updateVersionsAndClearEvents($aggregateRoot); + $this->_saveSnapshots($aggregateRoot); + } + + /** + * Store an entity in the identity map + * + * @param Entity $entity An Entity + * + * @return null + */ + private function _storeInIdentityMap(Entity $entity) + { + $this->_identityMap[(string) $entity->getGuid()] = $entity; + } + + /** + * Attempt to load an entity from the identity map + * + * @param Guid $guid An Entity's GUID + * + * @return \Tracks\Model\Entity + */ + private function _loadFromIdentityMap(Guid $guid) + { + return isset($this->_identityMap[(string) $guid]) + ? $this->_identityMap[(string) $guid] + : null; + } + + /** + * Load an entity from it's event history + * + * @param Guid $guid An Entity's GUID + * @param Entity $entity That Entity + * + * @return \Tracks\Model\Entity + */ + private function _loadFromHistory(Guid $guid, Entity $entity = null) + { + $events = $this->eventStore->getAllEvents($guid); + + if (is_null($entity)) { + if (is_null($modelClass = $this->eventStore->getType($guid))) { + return null; + } + + $entity = new $modelClass; + } + + $entity->loadHistory($events); + + return $entity; + } + + /** + * Load an entity into memory + * + * @param Guid $guid An Entity's GUID + * @param Entity $entity That Entity + * + * @return Tracks\Model\Entity + */ + private function _loadEntity(Guid $guid, Entity $entity = null) + { + $loadedEntity = $this->_loadFromIdentityMap($guid); + + if (is_null($loadedEntity)) { + $loadedEntity = $this->_loadFromSnapshot($guid); + } + + if (is_null($loadedEntity)) { + $loadedEntity = $this->_loadFromHistory($guid, $entity); + } + + if (is_null($loadedEntity)) { + return null; + } + + $this->_storeInIdentityMap($loadedEntity); + + return $loadedEntity; + } + + /** + * Route all new events on an aggregate to their appropriate handlers + * + * @param AggregateRoot $aggregateRoot An Aggregate Root + * + * @return null + */ + private function _routeEvents(AggregateRoot $aggregateRoot) + { + foreach ($aggregateRoot->getAllAppliedEvents() as $event) { + $this->router->route($event); + } + } + + /** + * Attempt to load an entity from a snapshot + * + * @param Guid $guid An Entity's GUID + * + * @return \Tracks\Model\Entity + */ + private function _loadFromSnapshot(Guid $guid) + { + if (is_null($entity = $this->snapshotStore->load($guid))) { + return null; + } + + $entity->loadHistory( + $this->eventStore->getEventsFromVersion( + $entity->getGuid(), + $entity->getVersion() + 1 + ) + ); + + return $entity; + } + + /** + * Update an entity's in-memory version and clear applied events + * + * This should be called after an entity's events have been saved. + * + * @param AggregateRoot $aggregateRoot An Aggregate Root + * + * @return null + */ + private function _updateVersionsAndClearEvents(AggregateRoot $aggregateRoot) + { + foreach ($aggregateRoot->getAllEntities() as $entity) { + $entity->incVersion(count($entity->getAppliedEvents())); + $entity->clearAppliedEvents(); + } + } + + /** + * Attempt to save a snapshot for all entites in an aggregate root + * + * @param AggregateRoot $aggregateRoot An Aggregate Root + * + * @return null + */ + private function _saveSnapshots(AggregateRoot $aggregateRoot) + { + foreach ($aggregateRoot->getAllEntities() as $entity) { + $this->_saveSnapshot($entity); + } + } + + /** + * Try to save a snapshot of an entity if frequency determines it is time + * + * @param Entity $entity An Entity + * + * @return null + */ + private function _saveSnapshot(Entity $entity) + { + $state = clone $entity; + $state->clearAppliedEvents(); + + $snapshot = $this->snapshotStore->load($state->getGuid()); + + if ((!$snapshot && $state->getVersion() >= $this->_snapshotFrequency) + || ($snapshot && ($state->getVersion() - $snapshot->getVersion()) >= $this->_snapshotFrequency) + ) { + $this->snapshotStore->save($entity); + } + } + + /** @var int */ + private $_snapshotFrequency; + + /** @var array */ + private $_identityMap = array(); } diff --git a/library/Tracks/EventStore/SnapshotStorage/File.php b/library/Tracks/EventStore/SnapshotStorage/File.php index 4c1c53f..a86ae85 100644 --- a/library/Tracks/EventStore/SnapshotStorage/File.php +++ b/library/Tracks/EventStore/SnapshotStorage/File.php @@ -1,31 +1,97 @@ + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ namespace Tracks\EventStore\SnapshotStorage; use Tracks\EventStore\ISnapshotStore; use Tracks\Model\Entity, Tracks\Model\Guid; -class File implements ISnapshotStore { - public function __construct($directory) { - $this->directory = $directory; - } +/** + * Filesystem-based Snapshot Storage + * + * @category Tracks + * @package EventStore + * @subpackage SnapshotStorage + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class File implements ISnapshotStore +{ - public function save(Entity $entity) { - file_put_contents($this->getFilename($entity->getGuid()), serialize($entity)); - } + /** + * Constructor + * + * @param string $directory Directory path + */ + public function __construct($directory) + { + assert('is_string($directory)'); + assert('file_exists($directory)'); + assert('is_writable($directory)'); + $this->_directory = $directory; + } - public function load(Guid $guid) { - if (file_exists($this->getFilename($guid))) { - return unserialize(file_get_contents($this->getFilename($guid))); - } else { - return NULL; - } - } + /** + * Save an Entity + * + * @param Entity $entity An Entity + * + * @return null + * @see Tracks\EventStore.ISnapshotStore::save() + */ + public function save(Entity $entity) + { + file_put_contents( + $this->_getFilename($entity->getGuid()), serialize($entity) + ); + } - private function getFilename(Guid $guid) { - return $this->directory .'/'. (string) $guid .'.dat'; - } + /** + * Load an Entity + * + * @param Guid $guid An Entity's GUID + * + * @return Entity|null + * @see Tracks\EventStore.ISnapshotStore::load() + */ + public function load(Guid $guid) + { + if (file_exists($this->_getFilename($guid))) { + return unserialize(file_get_contents($this->_getFilename($guid))); + } else { + return null; + } + } - /** @var string */ - private $directory; + /** + * Get the filename of an Entity by its GUID + * + * @param Guid $guid An Entity's GUID + * + * @return string + */ + private function _getFilename(Guid $guid) + { + return $this->_directory + .DIRECTORY_SEPARATOR + .(string) $guid + .'.dat'; + } + + /** @var string */ + private $_directory; } \ No newline at end of file diff --git a/library/Tracks/EventStore/SnapshotStorage/Memory.php b/library/Tracks/EventStore/SnapshotStorage/Memory.php index 07a4b14..94b9e07 100644 --- a/library/Tracks/EventStore/SnapshotStorage/Memory.php +++ b/library/Tracks/EventStore/SnapshotStorage/Memory.php @@ -1,42 +1,64 @@ - * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package EventStore + * @subpackage SnapshotStorage + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\EventStore\SnapshotStorage; use \Tracks\EventStore\ISnapshotStore; use \Tracks\Model\Guid, \Tracks\Model\Entity; -class Memory implements ISnapshotStore { - - /** - * Load a snapshot by guid - * - * @param Guid $guid - * @return Tracks\Model\Entity - */ - public function load(Guid $guid) { - if (isset($this->snapshots[(string) $guid])) { - return $this->snapshots[(string) $guid]; - } else { - return NULL; - } - } - - /** - * Save a snapshot of an entity - * - * @param Entity $entity - */ - public function save(Entity $entity) { - $this->snapshots[(string) $entity->getGuid()] = clone $entity; - } - - /** @var array */ - private $snapshots = array(); +/** + * In-Memory snapshot store implementation + * + * @category Tracks + * @package EventStore + * @subpackage SnapshotStorage + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class Memory implements ISnapshotStore +{ + + /** + * Load a snapshot by guid + * + * @param Guid $guid An Entity's GUID + * + * @return Tracks\Model\Entity + */ + public function load(Guid $guid) + { + if (isset($this->_snapshots[(string) $guid])) { + return $this->_snapshots[(string) $guid]; + } else { + return null; + } + } + + /** + * Save a snapshot of an entity + * + * @param Entity $entity An Entity + * + * @return null + */ + public function save(Entity $entity) + { + $this->_snapshots[(string) $entity->getGuid()] = clone $entity; + } + + /** @var array */ + private $_snapshots = array(); } \ No newline at end of file diff --git a/library/Tracks/Exception/Base.php b/library/Tracks/Exception/Base.php index df19ec6..dd5c040 100644 --- a/library/Tracks/Exception/Base.php +++ b/library/Tracks/Exception/Base.php @@ -1,6 +1,29 @@ + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ + namespace Tracks\Exception; -class Base extends Exception { - +/** + * Base Tracks Exception + * + * @category Tracks + * @package Exception + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class Base extends Exception +{ } \ No newline at end of file diff --git a/library/Tracks/Exception/HandlerAlreadyRegistered.php b/library/Tracks/Exception/HandlerAlreadyRegistered.php index f41e746..5d93805 100644 --- a/library/Tracks/Exception/HandlerAlreadyRegistered.php +++ b/library/Tracks/Exception/HandlerAlreadyRegistered.php @@ -1,25 +1,50 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package Exception + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\Exception; -class HandlerAlreadyRegistered extends Base { - - /** - * Constructor - * - * @param string $domainClass - * @param string $eventClass - * @param string $existingHandler - */ - public function __construct($domainClass, $eventClass, $existingHandler) { - parent::__construct("Handler already registered on object of type {$domainClass} for event {$eventClass}: {$existingHandler}"); - } +/** + * Handler Already Registered + * + * An attempt to register an event on a domain that has already been registered. + * + * @category Tracks + * @package Exception + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class HandlerAlreadyRegistered extends Base +{ + /** + * Constructor + * + * @param string $domainClass Classname of domain with the registered event + * @param string $eventClass Classname of event attempted to register + * @param string $existingHandler Classname of Event Handler + * + * @return null + */ + public function __construct($domainClass, $eventClass, $existingHandler) + { + assert('is_string($domainClass)'); + assert('is_string($eventClass)'); + assert('is_string($existingHandler)'); + parent::__construct( + 'Handler already registered on object of type '.$domainClass + .' for event '.$eventClass.': '.$existingHandler + ); + } } \ No newline at end of file diff --git a/library/Tracks/Model/AggregateRoot.php b/library/Tracks/Model/AggregateRoot.php index 4cd335c..ed1a82c 100644 --- a/library/Tracks/Model/AggregateRoot.php +++ b/library/Tracks/Model/AggregateRoot.php @@ -1,15 +1,32 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package Model + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\Model; -abstract class AggregateRoot extends Entity { +/** + * Aggregate root base class + * + * At this point, this is simply a marker to distinguish aggregate roots from + * other entities. + * + * @category Tracks + * @package Model + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +abstract class AggregateRoot extends Entity +{ } diff --git a/library/Tracks/Model/Entity.php b/library/Tracks/Model/Entity.php index 27c3e60..5a56cea 100644 --- a/library/Tracks/Model/Entity.php +++ b/library/Tracks/Model/Entity.php @@ -1,205 +1,258 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package Model + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\Model; use Tracks\Exception\HandlerAlreadyRegistered; -abstract class Entity { - - /** - * Load an array of events onto this entity - * - * @param array $history - */ - public function loadHistory(array $history) { - foreach ($history as $event) { - $this->handleDomainEvent($event); - $this->version++; - } - } - - /** - * Get this entity's guid - * - * @return Tracks\Model\Guid - */ - public function getGuid() { - return $this->guid; - } - - /** - * Get this entity's current version - * - * @return int - */ - public function getVersion() { - return $this->version; - } - - /** - * Increment this entity's version - */ - public function incVersion($increment = 1) { - $this->version += $increment; - } - - /** - * Get all newly applied events to this entity - * - * @return array - */ - public function getAppliedEvents() { - return $this->appliedEvents; - } - - /** - * Get all newly applied events to this entity and it's children - * - * @return array - */ - public function getAllAppliedEvents() { - $events = array(); - - foreach ($this as $property) { - if ($property instanceof Entity || $property instanceof EntityList) { - $events = array_merge($events, $property->getAllAppliedEvents()); - } - } - - return array_merge($this->appliedEvents, $events); - } - - /** - * Clear all applied events on this entity - */ - public function clearAppliedEvents() { - $this->appliedEvents = array(); - } - - /** - * Clear all applied events on this entity and it's children - */ - public function clearAllAppliedEvents() { - foreach ($this as $property) { - if ($property instanceof Entity || $property instanceof EntityList) { - $property->clearAllAppliedEvents(); - } - } - - $this->appliedEvents = array(); - } - - /** - * Has this entity been created? - * - * @return boolean - */ - public function isCreated() { - return !empty($this->guid); - } - - /** - * Has this entity been deleted? - * - * @return boolean - */ - public function isDeleted() { - return $this->deleted; - } - - /** - * Get all child entites of this entity - * - * @return array - */ - public function getAllChildEntities() { - $entities = array(); - - foreach ($this as $property) { - if ($property instanceof Entity || $property instanceof EntityList) { - $entities = array_merge($entities, $property->getAllEntities()); - } - } - - return $entities; - } - - /** - * Get all entities in the graph, starting from this one - * - * @return array - */ - public function getAllEntities() { - return array_merge(array($this), $this->getAllChildEntities()); - } - - /** - * Find a handler method registered with an event and call it if it exists - * - * @param \Tracks\Event\Base $event - */ - protected function handleDomainEvent(\Tracks\Event\Base $event) { - if ($handler_name = $this->getHandlerName($event)) { - $this->$handler_name($event); - } - } - - /** - * Apply a new event to this domain object - * - * @param \Tracks\Event\Base $event - */ - protected function applyEvent(\Tracks\Event\Base $event) { - $this->handleDomainEvent($event); - $this->appliedEvents[] = $event; - } - - /** - * Register an event handler on this domain object - * - * @param string $eventName - * @param string $handlerMethod - */ - protected function registerEvent($eventName, $handlerMethod) { - assert('method_exists($this, $handlerMethod)'); - - if (isset($this->handlers[$eventName])) { - throw new HandlerAlreadyRegistered(get_class($this), $eventName, $this->handlers[$eventName]); - } - - $this->handlers[$eventName] = $handlerMethod; - } - - /** - * Get the name of the handler method for an event - * - * @param \Tracks\Event\Base $event - * @return string - */ - private function getHandlerName(\Tracks\Event\Base $event) { - return (isset($this->handlers[get_class($event)]) ? $this->handlers[get_class($event)] : NULL); - } - - /** @var \Tracks\Model\Guid */ - protected $guid; - - /** @var boolean */ - protected $deleted = FALSE; - - /** @var int */ - protected $version = 0; - - /** @var array */ - protected $appliedEvents = array(); - - /** @var array */ - protected $handlers = array(); +/** + * Domain Entity base class + * + * @category Tracks + * @package Model + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +abstract class Entity +{ + + /** + * Load an array of events onto this entity + * + * @param array $history Set of Events + * + * @return null + */ + public function loadHistory(array $history) + { + foreach ($history as $event) { + $this->handleDomainEvent($event); + $this->version++; + } + } + + /** + * Get this entity's guid + * + * @return Tracks\Model\Guid + */ + public function getGuid() + { + return $this->guid; + } + + /** + * Get this entity's current version + * + * @return int + */ + public function getVersion() + { + return $this->version; + } + + /** + * Increment this entity's version + * + * @param int $increment Amount by which to increment the Entity's version + * + * @return null + */ + public function incVersion($increment = 1) + { + assert('is_int($increment)'); + $this->version += $increment; + } + + /** + * Get all newly applied events to this entity + * + * @return array + */ + public function getAppliedEvents() + { + return $this->appliedEvents; + } + + /** + * Get all newly applied events to this entity and it's children + * + * @return array + */ + public function getAllAppliedEvents() + { + $events = array(); + + foreach ($this as $property) { + if ($property instanceof Entity || $property instanceof EntityList) { + $events = array_merge($events, $property->getAllAppliedEvents()); + } + } + + return array_merge($this->appliedEvents, $events); + } + + /** + * Clear all applied events on this entity + * + * @return null + */ + public function clearAppliedEvents() + { + $this->appliedEvents = array(); + } + + /** + * Clear all applied events on this entity and it's children + * + * @return null + */ + public function clearAllAppliedEvents() + { + foreach ($this as $property) { + if ($property instanceof Entity || $property instanceof EntityList) { + $property->clearAllAppliedEvents(); + } + } + + $this->appliedEvents = array(); + } + + /** + * Has this entity been created? + * + * @return boolean + */ + public function isCreated() + { + return !empty($this->guid); + } + + /** + * Has this entity been deleted? + * + * @return boolean + */ + public function isDeleted() + { + return $this->deleted; + } + + /** + * Get all child entites of this entity + * + * @return array + */ + public function getAllChildEntities() + { + $entities = array(); + + foreach ($this as $property) { + if ($property instanceof Entity || $property instanceof EntityList) { + $entities = array_merge($entities, $property->getAllEntities()); + } + } + + return $entities; + } + + /** + * Get all entities in the graph, starting from this one + * + * @return array + */ + public function getAllEntities() + { + return array_merge(array($this), $this->getAllChildEntities()); + } + + /** + * Find a handler method registered with an event and call it if it exists + * + * @param \Tracks\Event\Base $event An Event + * + * @return null + */ + protected function handleDomainEvent(\Tracks\Event\Base $event) + { + if ($handler_name = $this->_getHandlerName($event)) { + $this->$handler_name($event); + } + } + + /** + * Apply a new event to this domain object + * + * @param \Tracks\Event\Base $event An Event + * + * @return null + */ + protected function applyEvent(\Tracks\Event\Base $event) + { + $this->handleDomainEvent($event); + $this->appliedEvents[] = $event; + } + + /** + * Register an event handler on this domain object + * + * @param string $eventName Classname of an Event + * @param string $handlerMethod Method name to register + * + * @return null + */ + protected function registerEvent($eventName, $handlerMethod) + { + assert('is_string($eventName)'); + assert('is_string($handlerMethod)'); + assert('method_exists($this, $handlerMethod)'); + + if (isset($this->handlers[$eventName])) { + throw new HandlerAlreadyRegistered(get_class($this), $eventName, $this->handlers[$eventName]); + } + + $this->handlers[$eventName] = $handlerMethod; + } + + /** + * Get the name of the handler method for an event + * + * @param \Tracks\Event\Base $event An Event + * + * @return string + */ + private function _getHandlerName(\Tracks\Event\Base $event) + { + return isset($this->handlers[get_class($event)]) + ? $this->handlers[get_class($event)] + : null; + } + + /** @var \Tracks\Model\Guid */ + protected $guid; + + /** @var boolean */ + protected $deleted = false; + + /** @var int */ + protected $version = 0; + + /** @var array */ + protected $appliedEvents = array(); + + /** @var array */ + protected $handlers = array(); } diff --git a/library/Tracks/Model/EntityList.php b/library/Tracks/Model/EntityList.php index 976f57a..e6efed6 100644 --- a/library/Tracks/Model/EntityList.php +++ b/library/Tracks/Model/EntityList.php @@ -1,160 +1,277 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package Model + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ +use Tracks\Model\Entity; namespace Tracks\Model; -class EntityList implements \ArrayAccess, \Iterator, \Countable { - - /** - * Add an entity to the list - * - * @param Entity $entity - */ - public function add(Entity $entity) { - $this->items[(string) $entity->getGuid()] = $entity; - $this->guids[] = (string) $entity->getGuid(); - } - - /** - * Find an entity by it's guid - * - * @param Guid $guid - * @return Tracks\Model\Entity - */ - public function find(Guid $guid) { - if (isset($this->items[(string) $guid])) { - return $this->items[(string) $guid]; - } else { - return NULL; - } - } - - /** - * Remove an entity from the list by guid - * - * @param Guid $guid - */ - public function remove(Guid $guid) { - $this->guids = array_splice($this->guids, array_search((string) $guid, $this->guids) - 1, 1); - unset($this->items[(string) $guid]); - } - - /** - * Get all newly applied events to entites in this list - * - * @return array - */ - public function getAllAppliedEvents() { - $events = array(); - - foreach ($this->items as $item) { - $events = array_merge($events, $item->getAllAppliedEvents()); - } - - return $events; - } - - /** - * Clear all applied events on entites in this list - */ - public function clearAllAppliedEvents() { - foreach ($this->items as $item) { - $item->clearAllAppliedEvents(); - } - } - - /** - * Get all entities in this list - * - * @return array - */ - public function getAllChildEntities() { - return $this->getAllEntities(); - } - - /** - * Get all entites in this list - * - * @return array - */ - public function getAllEntities() { - $entities = array(); - - foreach ($this->items as $entity) { - $entities = array_merge($entities, $entity->getAllEntities()); - } - - return $entities; - } - - - // - // ArrayAccess - // - - public function offsetExists($guid) { - return array_key_exists((string) $guid, $this->items); - } - - public function offsetGet($guid) { - return $this->find($guid); - } - - public function offsetSet($offset, $value) { - throw new Exception('Use EntityList::add() instead of setting an index'); - } - - public function offsetUnset($guid) { - $this->remove($guid); - } - - - // - // Iterator - // - - public function current() { - return $this->items[$this->guids[$this->cursor]]; - } - - public function key() { - return $this->guids[$this->cursor]; - } - - public function next() { - $this->cursor++; - } - - public function rewind() { - $this->cursor = 0; - } - - public function valid() { - return array_key_exists($this->cursor, $this->guids); - } - - - // - // Countable - // - - public function count() { - return count($this->items); - } - - /** @var array */ - private $items = array(); - - /** @var array */ - private $guids = array(); - - /** @var int */ - private $cursor = 0; +/** + * Entity list domain object + * + * Should be used to model a list of entities instead of a plain array. + * + * @category Tracks + * @package Model + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class EntityList +implements \ArrayAccess, \Iterator, \Countable +{ + + /** + * Add an entity to the list + * + * @param Entity $entity An Entity + * + * @return null + */ + public function add(Entity $entity) + { + $this->_items[(string) $entity->getGuid()] = $entity; + $this->_guids[] = (string) $entity->getGuid(); + } + + /** + * Find an entity by it's guid + * + * @param Guid $guid An Entity's GUID + * + * @return Tracks\Model\Entity + */ + public function find(Guid $guid) + { + if (isset($this->_items[(string) $guid])) { + return $this->_items[(string) $guid]; + } else { + return null; + } + } + + /** + * Remove an entity from the list by guid + * + * @param Guid $guid An Entity's GUID + * + * @return null + */ + public function remove(Guid $guid) + { + $this->_guids = array_splice( + $this->_guids, + array_search((string) $guid, $this->_guids) - 1, + 1 + ); + unset($this->_items[(string) $guid]); + } + + /** + * Get all newly applied events to entites in this list + * + * @return array + */ + public function getAllAppliedEvents() + { + $events = array(); + + foreach ($this->_items as $item) { + $events = array_merge($events, $item->getAllAppliedEvents()); + } + + return $events; + } + + /** + * Clear all applied events on entites in this list + * + * @return null + */ + public function clearAllAppliedEvents() + { + foreach ($this->_items as $item) { + $item->clearAllAppliedEvents(); + } + } + + /** + * Get all entities in this list + * + * @return array + */ + public function getAllChildEntities() + { + return $this->getAllEntities(); + } + + /** + * Get all entites in this list + * + * @return array + */ + public function getAllEntities() + { + $entities = array(); + + foreach ($this->_items as $entity) { + $entities = array_merge($entities, $entity->getAllEntities()); + } + + return $entities; + } + + + // + // ArrayAccess + // + + /** + * offsetExists + * + * @param Guid $guid An Entity's GUID + * + * @return boolean + * @see ArrayAccess::offsetExists() + */ + public function offsetExists($guid) + { + return array_key_exists((string) $guid, $this->_items); + } + + /** + * offsetGet + * + * @param Guid $guid An Entity's GUID + * + * @return Entity + * @see ArrayAccess::offsetGet() + */ + public function offsetGet($guid) + { + return $this->find($guid); + } + + /** + * offsetSet + * + * @param mixed $offset Any + * @param mixed $value Any + * + * @return null + * @throws LogicException + * @see ArrayAccess::offsetSet() + */ + public function offsetSet($offset, $value) + { + throw new LogicException('Use EntityList::add() instead of setting an index'); + } + + /** + * offsetUnset + * + * @param Guid $guid An Entity's GUID + * + * @return null + * @see ArrayAccess::offsetUnset() + */ + public function offsetUnset($guid) + { + $this->remove($guid); + } + + + // + // Iterator + // + + /** + * current + * + * @return Entity + * @see Iterator::current() + */ + public function current() + { + return $this->_items[$this->_guids[$this->_cursor]]; + } + + /** + * key + * + * @return Guid + * @see Iterator::key() + */ + public function key() + { + return $this->_guids[$this->_cursor]; + } + + /** + * next + * + * @return null + * @see Iterator::next() + */ + public function next() + { + $this->_cursor++; + } + + /** + * rewind + * + * @return null + * @see Iterator::rewind() + */ + public function rewind() + { + $this->_cursor = 0; + } + + /** + * valid + * + * @return boolean + * @see Iterator::valid() + */ + public function valid() + { + return array_key_exists($this->_cursor, $this->_guids); + } + + + // + // Countable + // + + /** + * count + * + * @return int + * @see Countable::count() + */ + public function count() + { + return count($this->_items); + } + + /** @var array */ + private $_items = array(); + + /** @var array */ + private $_guids = array(); + + /** @var int */ + private $_cursor = 0; } diff --git a/library/Tracks/Model/Guid.php b/library/Tracks/Model/Guid.php index fd45524..c61969c 100644 --- a/library/Tracks/Model/Guid.php +++ b/library/Tracks/Model/Guid.php @@ -1,44 +1,67 @@ + * Tracks CQRS Framework + * + * PHP Version 5.3 + * + * @category Tracks + * @package Model + * @author Sean Crystal * @copyright 2011 Sean Crystal - * @license http://www.opensource.org/licenses/BSD-3-Clause - * @link https://github.com/spiralout/Tracks + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks */ namespace Tracks\Model; -class Guid { +/** + * Globally-Unique Identifier (GUID) implementation + * + * All entites must have a guid. + * + * @category Tracks + * @package Model + * @author Sean Crystal + * @copyright 2011 Sean Crystal + * @license http://www.opensource.org/licenses/BSD-3-Clause BSD 3-Clause + * @link https://github.com/spiralout/Tracks + */ +class Guid +{ - /** - * Constructor - * - * @param string $guid - */ - public function __construct($guid = NULL) { - $this->guid = $guid; - } + /** + * Constructor + * + * @param string|null $guid A GUID + * + * @return null + */ + public function __construct($guid = null) + { + assert('is_string($guid) || is_null($guid)'); + $this->guid = $guid; + } - /** - * Return a string representation of this guid - * - * @return string - */ - public function __toString() { - return $this->guid; - } + /** + * Return a string representation of this guid + * + * @return string + */ + public function __toString() + { + return $this->guid; + } - /** - * Guid factory method - * - * @return \Tracks\Model\Guid - */ - static public function create() { - return new self(uniqid('', TRUE)); - } + /** + * Guid factory method + * + * @return \Tracks\Model\Guid + */ + static public function create() + { + return new self(uniqid('', true)); + } - /** @var string */ - public $guid; + /** @var string */ + public $guid; }