Skip to content

Instantly share code, notes, and snippets.

@texdc
Last active August 29, 2015 13:56
Show Gist options
  • Save texdc/8963676 to your computer and use it in GitHub Desktop.
Save texdc/8963676 to your computer and use it in GitHub Desktop.
<?php
namespace Category\Command;
use Category\CategoryId;
use Message\AbstractMessage;
use Message\Message;
use Message\MessageId;
use Message\MessageTrait;
use Product\Command\ProductsCommandTrait;
interface Command extends Message
{
public function categoryId();
}
class CommandId extends MessageId
{
const ADD_PRODUCTS_TO_CATEGORY = 'AddProductsToCategory';
const REMOVE_PRODUCTS_FROM_CATEGORY = 'RemoveProductsFromCategory';
protected static $validNames = [
self::ADD_PRODUCTS_TO_CATEGORY,
self::REMOVE_PRODUCTS_FROM_CATEGORY
];
}
abstract class AbstractCommand extends AbstractMessage implements Command
{
use MessageTrait {
setAggregateRootId as private setCategoryId;
aggregateRootId as categoryId;
}
public function __construct(CategoryId $categoryId)
{
parent::__construct(new CommandId(static::$messageName));
$this->setCategoryId($categoryId);
}
public function __toString()
{
return sprintf(
'%s [categoryId: %s, timestamp: %s]',
$this->messageName(),
$this->aggregateRootId ?: 'N/A',
$this->messageTime()->format('Y-m-d\TH:i:s.u')
);
}
}
abstract class AbstractProductsCommand extends AbstractCommand
{
use ProductsCommandTrait;
protected $productIds;
public function __construct(CategoryId $categoryId, array $productIds)
{
parent::__construct($categoryId);
$this->setProductIds($productIds);
}
}
class AddProductsToCategoryCommand extends AbstractProductsCommand
{
final protected static $messageName = CommandId::ADD_PRODUCTS_TO_CATEGORY;
}
class RemoveProductsFromCategoryCommand extends AbstractProductsCommand
{
final protected static $messageName = CommandId::REMOVE_PRODUCTS_FROM_CATEGORY;
}
<?php
namespace Category\Controller;
use Category\CategoryId;
use Category\Command\CommandCenter;
use Http\Status;
use Platform\Controller\RestController;
use Product\ProductId;
class ProductsController extends RestController
{
/**
* @var CommandCenter
*/
protected $commandCenter;
public function put($id, array $data)
{
$categoryId = CategoryId::fromString($id);
$productIds = $this->getProductIds($data['productIds']);
$commandId = $this->commandCenter->addProductsToCategory($categoryId, $productIds);
return Status::202(compact('commandId'));
}
public function delete(array $data)
{
$categoryId = CategoryId::fromString($data['categoryId']);
$productIds = $this->getProductIds($data['productIds']);
$commandId = $this->commandCenter->removeProductsFromCategory($categoryId, $productIds);
return Status::202(compact('commandId'));
}
private function getProductIds(array $dataIds)
{
$convertIds = function($productId) {
return ProductId::fromString($productId);
};
return array_map($convertIds, $dataIds);
}
}
<?php
namespace Message;
use DateTime;
use DomainException;
interface Message
{
public function messageId();
public function messageName();
public function messageTime();
}
/**
* A rich and expressive unique identifier that forms the heart of every message.
*/
class MessageId
{
protected static $partSeperator = '_';
protected static $hashAlgorithm = 'crc32';
protected static $validNames = [];
protected $name;
protected $time;
protected $hash;
public function __construct($name)
{
$this->time = microtime(true);
$this->name = trim($name);
$this->hash = hash(static::$hashAlgorithm, rand(0, $this->time));
if (empty($this->name)) {
throw new DomainException('Name must not be empty');
}
if (!in_array($this->name, static::$validNames)) {
throw new DomainException("Invalid name [$name]");
}
}
public static function fromString($idString)
{
$parts = explode(static::$partSeperator, $idString);
if (count($parts) != 3) {
throw new DomainException("Unexpected format [$idString]");
}
return static::fromParts($parts[0], $parts[1], $parts[2]);
}
public static function fromParts($name, $time, $hash)
{
$id = new static($name);
$id->time = floatval($time);
$id->hash = strval($hash);
return $id;
}
public function name()
{
return $this->name;
}
public function time()
{
return $this->time;
}
public function equals(self $other)
{
return ((string) $this == (string) $other);
}
public function __toString()
{
return implode(static::$partSeperator, (array) $this);
}
}
trait MessageTrait
{
private function setAggregateRootId($aggregateRootId)
{
$this->aggregateRootId = $aggregateRootId;
}
public function aggregateRootId()
{
return $this->aggregateRootId;
}
public function messageId()
{
return $this->messageId;
}
public function messageName()
{
return $this->messageId->name();
}
public function messageTime()
{
return DateTime::createFromFormat('U.u', $this->messageId->time());
}
}
abstract class AbstractMessage implements Message
{
protected static $messageName;
protected $aggregateRootId;
protected $messageId;
public function __construct(MessageId $messageId = null)
{
$this->messageId = $messageId ?: new MessageId(static::$messageName);
}
public function equals(self $other)
{
return $this->messageId->equals($other->messageId);
}
}
interface MessageBus
{
public function append(Message $aMessage);
public function clear();
public function deliver(Dispatcher $withDispatcher);
}
interface Dispatcher
{
public function dispatch(Message $aMessage);
}
<?php
namespace Product\Command;
use DomainException;
use Product\ProductId;
trait ProductsCommandTrait
{
public function productIds()
{
return $this->productIds;
}
private function setProductIds(array $productIds)
{
$this->productIds = [];
foreach (array_unique($productIds) as $productId) {
$this->assertProductId($productId);
$this->productIds[] = $productId;
}
}
private function assertProductId($value)
{
if (!($value instanceof ProductId)) {
$message = sprintf(
'Expected ProductId, not %s',
is_object($value) ? get_class($value) : gettype($value)
);
throw new DomainException($message);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment