Skip to content

Commit

Permalink
Merge pull request #9 from young-steveo/conveyor
Browse files Browse the repository at this point in the history
Initial, broken, untested CQRS implementation
  • Loading branch information
young-steveo committed Jun 6, 2023
2 parents 932e953 + d4d0ba2 commit 841e095
Show file tree
Hide file tree
Showing 22 changed files with 386 additions and 108 deletions.
23 changes: 12 additions & 11 deletions src/Cabinet/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ public function service(string $serviceName, string|null $implementation = null)
if ($implementation === null) {
$implementation = $serviceName;
}
$this->factory($serviceName, function (Container $container) use ($implementation) {
return $container->resolver->resolve($implementation);
});
$this->factory($serviceName, $this->simpleFactory($implementation));
}

/**
Expand Down Expand Up @@ -129,9 +127,7 @@ public function instance(string $serviceName, mixed $instance): void
*/
public function prototype(string $serviceName): void
{
$this->prototypeFactory($serviceName, function (Container $container) use ($serviceName) {
return $container->resolver->resolve($serviceName);
});
$this->prototypeFactory($serviceName, $this->simpleFactory($serviceName));
}

/**
Expand Down Expand Up @@ -183,6 +179,14 @@ public function middleware(string $serviceName, Progression $middleware): void
$this->middlewares[$serviceName] = $this->middlewares[$serviceName]->add($middleware);
}

/**
* @param class-string $serviceName
*/
protected function simpleFactory(string $serviceName): \Closure
{
return fn(Container $container) => $container->resolver->resolve($serviceName);
}

/**
* ArrayAccess methods
*/
Expand Down Expand Up @@ -227,7 +231,6 @@ public function offsetUnset($offset): void
*
* @param class-string $offset
* @throws Error\InvalidKey
* @throws Error\OutOfBounds
*/
public function offsetGet($offset): mixed
{
Expand All @@ -236,11 +239,9 @@ public function offsetGet($offset): mixed
}

// Provide the instance.
$provider = $this->providers[$offset] ?? new NullProvider();
$provider = $this->providers[$offset] ?? PrototypeProvider::fromFactory($this->simpleFactory($offset));

$instance = $provider($this);
if (!$instance) {
throw new Error\OutOfBounds("No entry was found for this identifier: $offset");
}

// Apply decorators.
if (isset($this->decorators[$offset])) {
Expand Down
15 changes: 0 additions & 15 deletions src/Cabinet/Error/OutOfBounds.php

This file was deleted.

16 changes: 0 additions & 16 deletions src/Cabinet/NullProvider.php

This file was deleted.

2 changes: 1 addition & 1 deletion src/Cabinet/PrototypeProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static function fromFactory(\Closure $factory): self
/**
* Provide a service from the factory.
*/
public function __invoke(Container $container): object|null
public function __invoke(Container $container): object
{
return ($this->factory)($container);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Cabinet/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ abstract class Provider
/**
* Invoking the provider should return the service.
*/
abstract public function __invoke(Container $container): object|null;
abstract public function __invoke(Container $container): object;
}
2 changes: 1 addition & 1 deletion src/Cabinet/SimpleProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static function fromFactory(\Closure $factory): static
/**
* Provide a service from the factory.
*/
public function __invoke(Container $container): object|null
public function __invoke(Container $container): object
{
if (!isset($this->service)) {
$this->service = ($this->factory)($container);
Expand Down
16 changes: 16 additions & 0 deletions src/Conveyor/Bus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Arcanum\Conveyor;

interface Bus
{
/**
* Dispatch an object to a handler.
*
* The object to be dispatched should be a simple DTO, and the
* return value should be a simple DTO.
*/
public function dispatch(object $object): object;
}
56 changes: 56 additions & 0 deletions src/Conveyor/DTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Arcanum\Conveyor;

/**
* DTO validates that an object is a simple DTO.
*/
final class DTO implements Validator
{
/**
* Validate a DTO.
*/
public function validate(object $object): void
{
$image = new \ReflectionClass($object);
$name = $image->getName();

if (!$image->isInstantiable()) {
throw new InvalidDTO("$name is not instantiable.");
}

if (!$image->isFinal()) {
throw new InvalidDTO("$name is not final.");
}

$properties = $image->getProperties();

foreach ($properties as $property) {
if (!$property->isPublic()) {
$propertyName = $property->getName();
throw new InvalidDTO("$name has non-public property $propertyName.");
}

if ($property->isStatic()) {
$propertyName = $property->getName();
throw new InvalidDTO("$name has static property $propertyName.");
}

if (!$property->isReadOnly()) {
$propertyName = $property->getName();
throw new InvalidDTO("$name has non-read-only property $propertyName.");
}
}

$methods = $image->getMethods();

foreach ($methods as $method) {
if ($method->isPublic()) {
$methodName = $method->getName();
throw new InvalidDTO("$name has public method $methodName.");
}
}
}
}
12 changes: 12 additions & 0 deletions src/Conveyor/EmptyDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Arcanum\Conveyor;

/**
* Empty is a DTO that has no data.
*/
final class EmptyDTO
{
}
13 changes: 13 additions & 0 deletions src/Conveyor/InvalidDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Arcanum\Conveyor;

final class InvalidDTO extends \InvalidArgumentException
{
public function __construct(string $message = '', int $code = 0, \Throwable $previous = null)
{
parent::__construct("Invalid DTO: $message", $code, $previous);
}
}
51 changes: 51 additions & 0 deletions src/Conveyor/StrictBus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Arcanum\Conveyor;

use Arcanum\Cabinet\Container;

class StrictBus extends SwiftBus
{
/**
* @var Validator[]
*/
protected array $validators;

/**
* StrictBus
*
* This bus validates that the object being dispatched passes the validators
* and that the response from the handler passes the validators.
*/
public function __construct(protected Container $container, Validator ...$validators)
{
parent::__construct($container);
$this->validators = $validators;
}

/**
* Dispatch an object to a handler.
*/
public function dispatch(object $object): object
{
$this->validateObject($object);
$response = parent::dispatch($object);
$this->validateObject($response);
return $response;
}

/**
* Validate that an object is a simple DTO.
*
* A simple DTO is a class that is final, has only public, non-static,
* read-only properties, and has no public methods.
*/
protected function validateObject(object $object): void
{
foreach ($this->validators as $validator) {
$validator->validate($object);
}
}
}
46 changes: 46 additions & 0 deletions src/Conveyor/SwiftBus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Arcanum\Conveyor;

use Arcanum\Cabinet\Container;

class SwiftBus implements Bus
{
public function __construct(protected Container $container)
{
}

/**
* Dispatch an object to a handler.
*/
public function dispatch(object $object): object
{
return $this->handlerFor($object)($object) ?? new EmptyDTO();
}

/**
* Get the handler for an object.
*
* SwiftBus assumes that the handler is a callable.
*/
protected function handlerFor(object $object): callable
{
/** @var callable */
return $this->container->get($this->handlerNameFor($object));
}

/**
* Get the handler name for an object.
*
* This is the class name of the object with the suffix "Handler".
*
* @return class-string
*/
protected function handlerNameFor(object $object): string
{
/** @var class-string */
return get_class($object) . 'Handler';
}
}
13 changes: 13 additions & 0 deletions src/Conveyor/Validator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Arcanum\Conveyor;

interface Validator
{
/**
* Validate an object
*/
public function validate(object $object): void;
}
Loading

0 comments on commit 841e095

Please sign in to comment.