diff --git a/app/AggregateHistory.php b/app/AggregateHistory.php new file mode 100644 index 0000000000000000000000000000000000000000..11c34167f516dc52384fcf8edf3ea790b9ba09b2 --- /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 0000000000000000000000000000000000000000..5ed490db8918a7a8cb02326fac5021bc70215f0e --- /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 2886688d9f447d1371ba66803bec49efef51903e..2fe9830f42877d0a3363acf269ed4d337d2f9b86 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