From 6c786e6a1739feeff921b9e7d113b0cd5c359a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kucha=C5=99?= <honza.kuchar@grifart.cz> Date: Sun, 21 Feb 2016 12:21:53 +0100 Subject: [PATCH] Basket (green): Add ability to restore basket from historical events --- app/AggregateHistory.php | 29 ++++++++++++++++ app/aggreagates/AbstractAggregate.php | 35 +++++++++++++++++++ app/aggreagates/Basket.php | 50 ++++++++++++++++++++++----- 3 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 app/AggregateHistory.php create mode 100644 app/aggreagates/AbstractAggregate.php diff --git a/app/AggregateHistory.php b/app/AggregateHistory.php new file mode 100644 index 0000000..11c3416 --- /dev/null +++ b/app/AggregateHistory.php @@ -0,0 +1,29 @@ +<?php + +/** + * Collection of domain events + */ +class AggregateHistory extends ArrayObject { + + private $aggregateId; + + public function __construct($aggregateId, DomainEvents $domainEvents) + { + $this->aggregateId = $aggregateId; + + // type check for IDomainEvent[] + array_filter((array) $domainEvents, function(IDomainEvent $domainEvent) {}); + + parent::__construct((array) $domainEvents); + } + + /** + * @return array|null|object + */ + public function getAggregateId() + { + return $this->aggregateId; + } + + +} \ No newline at end of file diff --git a/app/aggreagates/AbstractAggregate.php b/app/aggreagates/AbstractAggregate.php new file mode 100644 index 0000000..5ed490d --- /dev/null +++ b/app/aggreagates/AbstractAggregate.php @@ -0,0 +1,35 @@ +<?php + +abstract class AbstractAggregate +{ + + /** + * @param \IDomainEvent $domainEvent + * @return string + */ + private function getApplyMethodForDomainEvent(IDomainEvent $domainEvent) + { + return "apply" . get_class($domainEvent); + } + + /** + * Apply domain event if this aggregate accepts this event + * @param \IDomainEvent $domainEvent + * @internal + */ + public function applyIfAccepts(IDomainEvent $domainEvent) { + if(method_exists($this, $this->getApplyMethodForDomainEvent($domainEvent))) { + $this->apply($domainEvent); + } + } + + /** + * Apply domain event; if objects does not accepts this event -> fail + * @param \IDomainEvent $domainEvent + */ + public function apply(IDomainEvent $domainEvent) { + $method = $this->getApplyMethodForDomainEvent($domainEvent); + $this->$method($domainEvent); + } + +} \ No newline at end of file diff --git a/app/aggreagates/Basket.php b/app/aggreagates/Basket.php index 2886688..2fe9830 100644 --- a/app/aggreagates/Basket.php +++ b/app/aggreagates/Basket.php @@ -1,6 +1,6 @@ <?php -final class Basket implements RecordsEvents +final class Basket extends AbstractAggregate implements RecordsEvents { /** @var BasketId $basketId */ @@ -29,6 +29,17 @@ final class Basket implements RecordsEvents return $basket; } + public static function reconstituteFrom(AggregateHistory $aggregateHistory) + { + $basketId = $aggregateHistory->getAggregateId(); + $basket = new static($basketId); + + foreach($aggregateHistory as $event) { + $basket->apply($event); + } + return $basket; + } + // ------------- public interface -------------- public function addProduct(ProductId $productId, $name) { @@ -40,11 +51,7 @@ final class Basket implements RecordsEvents new ProductWasAddedToBasket($this->basketId, $productId, $name) ); - // Update internal tracked state - if(!$this->isProductInBasket($productId)) { - $this->itemsCountById[(string)$productId] = 0; - } - $this->itemsCountById[(string)$productId]++; + // Internal state NOT changed directly @see apply() method } public function removeProduct(ProductId $productId) @@ -59,8 +66,34 @@ final class Basket implements RecordsEvents new ProductWasRemovedFromBasket($this->basketId, $productId) ); - // Update internal tracked state - $this->itemsCountById[(string)$productId]--; + // Internal state NOT changed directly @see apply() method + } + + // --------- object state transitions based on incoming events --------- + + /** + * @param \ProductWasAddedToBasket $productWasAddedToBasket + */ + public function applyProductWasAddedToBasket(ProductWasAddedToBasket $productWasAddedToBasket) + { + $productId = $productWasAddedToBasket->getProductId(); + if (!$this->isProductInBasket($productId)) { + $this->itemsCountById[(string) $productId] = 0; + } + $this->itemsCountById[(string) $productId]++; + } + + /** + * @param \ProductWasRemovedFromBasket $productWasRemovedFromBasket + */ + public function applyProductWasRemovedFromBasket(ProductWasRemovedFromBasket $productWasRemovedFromBasket) + { + $productId = $productWasRemovedFromBasket->getProductId(); + $this->itemsCountById[(string) $productId]--; + } + + public function applyBasketWasPickedUp() + { } // ------------ guarding helpers -------------------- @@ -93,6 +126,7 @@ final class Basket implements RecordsEvents private function recordThat(IDomainEvent $domainEvent) { $this->recordedEvents[] = $domainEvent; + $this->apply($domainEvent); } } \ No newline at end of file -- GitLab