-
-
Save beberlei/53cd6580d87b1f5cd9ca to your computer and use it in GitHub Desktop.
Doctrine + DomainEvents
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
vendor/ | |
db.sqlite | |
composer.lock |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"require": { | |
"doctrine/orm": "@stable" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\Domain; | |
use Doctrine\ORM\Mapping as ORM; | |
use MyProject\DomainSuperTypes\AggregateRoot; | |
/** | |
* @Entity | |
*/ | |
class InventoryItem extends AggregateRoot | |
{ | |
/** | |
* @Id @GeneratedValue @Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @Column | |
*/ | |
private $name; | |
/** | |
* @Column(type="integer") | |
*/ | |
private $counter = 0; | |
public function __construct($name) | |
{ | |
$this->name = $name; | |
$this->raise('InventoryItemCreated', array('name' => $name)); | |
} | |
public function rename($name) | |
{ | |
$this->name = $name; | |
$this->raise('InventoryItemRenamed', array('name' => $name)); | |
} | |
public function checkIn($count) | |
{ | |
$this->counter += $count; | |
$this->raise('ItemsCheckedIntoInventory', array('count' => $count)); | |
} | |
public function remove($count) | |
{ | |
$this->counter -= $count; | |
$this->raise('ItemsRemovedFromInventory', array('count' => $count)); | |
} | |
} | |
class EchoInventoryListener | |
{ | |
public function onInventoryItemCreated($event) | |
{ | |
printf("New item created with name %s\n", $event->name); | |
} | |
public function onInventoryItemRenamed($event) | |
{ | |
printf("Item was renamed to %s\n", $event->name); | |
} | |
public function onItemsCheckedIntoInventory($event) | |
{ | |
printf("There were %d new items checked into inventory\n", $event->count); | |
} | |
public function onItemsRemovedFromInventory($event) | |
{ | |
printf("There were %d items removed from inventory\n", $event->count); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Doctrine\ORM\Tools\Setup; | |
use Doctrine\ORM\Tools\SchemaTool; | |
use Doctrine\ORM\EntityManager; | |
use Doctrine\Common\EventManager; | |
use MyProject\DomainEvents\DirectEventDispatcher; | |
use MyProject\DomainEvents\DomainEventListener; | |
use MyProject\Domain\InventoryItem; | |
use MyProject\Domain\EchoInventoryListener; | |
require_once __DIR__ . "/vendor/autoload.php"; | |
require_once "DomainEventDispatcher.php"; | |
require_once "DomainSuperTypes.php"; | |
require_once "Domain.php"; | |
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__)); | |
$conn = array( | |
'driver' => 'pdo_sqlite', | |
'path' => __DIR__ . '/db.sqlite', | |
); | |
$evm = new EventManager(); | |
$evm->addEventListener( | |
array('postInsert', 'postUpdate', 'postRemove', 'postFlush'), | |
new DomainEventListener() | |
); | |
$evm->addEventListener( | |
array('onInventoryItemCreated', 'onInventoryItemRenamed', 'onItemsRemovedFromInventory', 'onItemsCheckedIntoInventory'), | |
new EchoInventoryListener | |
); | |
$entityManager = EntityManager::create($conn, $config, $evm); | |
$schemaTool = new SchemaTool($entityManager); | |
try { | |
$schemaTool->createSchema(array($entityManager->getClassMetadata(InventoryItem::CLASS))); | |
} catch(\Exception $e) { | |
} | |
$item = new InventoryItem('Cookies'); | |
$item->checkIn(10); | |
$entityManager->persist($item); | |
$entityManager->flush(); | |
$item->rename('Chocolate Cookies'); | |
$item->remove(5); | |
$entityManager->flush(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\DomainEvents; | |
use Doctrine\ORM\EntityManager; | |
use MyProject\DomainSuperTypes\AggregateRoot; | |
class DomainEventListener | |
{ | |
private $entities = array(); | |
public function postPersist($event) | |
{ | |
$this->keepAggregateRoots($event); | |
} | |
public function postUpdate($event) | |
{ | |
$this->keepAggregateRoots($event); | |
} | |
public function postRemove($event) | |
{ | |
$this->keepAggregateRoots($event); | |
} | |
public function postFlush($event) | |
{ | |
$entityManager = $event->getEntityManager(); | |
$evm = $entityManager->getEventManager(); | |
foreach ($this->entities as $entity) { | |
$class = $entityManager->getClassMetadata(get_class($entity)); | |
foreach ($entity->popEvents() as $event) { | |
$event->setAggregate($class->name, $class->getSingleIdReflectionProperty()->getValue($entity)); | |
$evm->dispatchEvent("on" . $event->getName(), $event); | |
} | |
} | |
$this->entities = array(); | |
} | |
private function keepAggregateRoots($event) | |
{ | |
$entity = $event->getEntity(); | |
if (!($entity instanceof AggregateRoot)) { | |
return; | |
} | |
$this->entities[] = $entity; | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\DomainSuperTypes; | |
use Doctrine\Common\EventArgs; | |
abstract class AggregateRoot | |
{ | |
private $events = array(); | |
public function popEvents() | |
{ | |
$events = $this->events; | |
$this->events = array(); | |
return $events; | |
} | |
protected function raise($eventName, array $properties) | |
{ | |
$this->events[] = new DomainEvent($eventName, $properties); | |
} | |
} | |
class DomainEvent extends EventArgs | |
{ | |
private $eventName; | |
private $properties; | |
private $aggregateClass; | |
private $aggregateId; | |
private $time; | |
public function __construct($eventName, array $properties) | |
{ | |
$this->eventName = $eventName; | |
$this->properties = $properties; | |
$this->time = microtime(true); | |
} | |
public function getTime() | |
{ | |
return $this->time; | |
} | |
public function getName() | |
{ | |
return $this->eventName; | |
} | |
public function __get($name) | |
{ | |
if (!isset($this->properties[$name])) { | |
throw new \RuntimeException("Property '" . $name . "' does not exist on event '" . $this->eventName); | |
} | |
return $this->properties[$name]; | |
} | |
public function setAggregate($class, $id) | |
{ | |
$this->aggregateClass = $class; | |
$this->aggregateId = $id; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I know I'm three years later in this, but I'm implementing Domain Events with Slim Framework + Doctrine ORM + DDD and used parts of your code, except I wrote a publisher to which I registered subscribers that listen to specific domain events. Anyway, you have a typo with
postInsert
in:It should be
postPersist
. I was scratching my head about why it wasn't triggered, but flush was.