Skip to content

Commit

Permalink
Merge 4e12273 into 726aaf5
Browse files Browse the repository at this point in the history
  • Loading branch information
awd-studio committed Jun 3, 2020
2 parents 726aaf5 + 4e12273 commit 01a45b4
Show file tree
Hide file tree
Showing 77 changed files with 2,429 additions and 1,984 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
language: php

php:
- 7.2
- 7.3
- 7.4
- nightly

sudo: false

Expand All @@ -18,6 +18,8 @@ script:

matrix:
fast_finish: true
allow_failures:
- php: nightly

notifications:
on_success: never
Expand Down
175 changes: 43 additions & 132 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,164 +1,75 @@
# Service buses in PHP

## A simple library, to implement CQRS pattern with PHP projects.
## A simple library, to implement `CQRS`-ish pattern on PHP projects.

[![Build Status](https://travis-ci.org/awd-studio/service-buses.svg?branch=master)](https://travis-ci.org/awd-studio/service-buses)
[![Coverage Status](https://coveralls.io/repos/github/awd-studio/service-buses/badge.svg?branch=master)](https://coveralls.io/github/awd-studio/service-buses?branch=master)

#### Advantages:
- Provides such kind of service-buses as: `Command Bus`, `Query Bus` and `Event Bus`, for the CQRS pattern implementing.
- In a single package.
- Driven by a `Dependency Injection Container` (uses the standard - PSR-11).
#### Features:
- Nither messages nor handlers don't need to extend or implement any additional abstraction.
- Supports `middleware` for handlers.
- A handler (as well as middleware) can be any of `callable` item.
- Handlers can subscribe on any of parents or implementations of an event.
- Contains a decorator to register handles as services handled via `PSR-11`'s container.
- Contains a decorator to autosubscribe handlers by a typehint on a message that it handles.
- Provides ready to go bus patterns such a `Command Bus`, a `Query Bus` and an `Event Bus`.
- Supports passing additional parameters to the buses to send to handlers.

#### Contents:
- [Requirements](#requirements)
- [Usage](#usage)
- [Global configuration](#configuration)
- [Get started](#get-started)
- [Handling messages](#handling-messages)
- [Predefined buses](#predefined-buses)
- [Command Bus](#command-bus)
- [Query Bus](#query-bus)
- [Event Bus](#event-bus)
- [Define custom bus](#define-custom-bus)
- [Testing](#testing)

-----

## Requirements:
## Get started:

- PHP v7.1+
- [Composer](https://getcomposer.org/) package manager
- [PSR-11](https://github.com/php-fig/container)\-compatible container
### Requirenments:
- PHP 7.3+
- [PSR-11](https://github.com/php-fig/container) - compatible container (*optional*)


## Usage:

### Configuration:
```php
<?php

use AwdStudio\ServiceBuses\Implementation\Handling\ContainerHandlerLocator;
use AwdStudio\ServiceBuses\Implementation\Handling\InMemoryHandlerLocator;
use AwdStudio\ServiceBuses\Implementation\Middleware\ChannelChain;
use AwdStudio\ServiceBuses\Implementation\Middleware\Chain;

// Middleware also must be invokable
class MyMiddleware
{
// You can use types to define processing command
public function __invoke(MyCommand $command, callable $next): void
{
// Preprocess the command ...
$result = $next($command);
// Or postprocess the command ...

return $result; // Return the result
}
}


// You can use predefined handler locators
$handlers = new InMemoryHandlerLocator([
MyCommand::class => new MyCommandHandler() // Assign a handler to the command it manages
]);

// There also is a handler locator to work with a service-container
$handlers = new ContainerHandlerLocator($myPsr11Container);
$handlers->add(MyCommand::class, MyCommandHandler::class);


// To fill middleware there are also a couple of predefined options

// A simple middleware chain:
$middlewareChain = new Chain();
$middlewareChain->add(new MyMiddleware());

// A chain which processes middleware by channels (by processed command):
$middlewareChain = new ChannelChain();
$middlewareChain->add(new MyMiddleware()); // Will be called only for the MyCommand
### Install:
```sh
composer require awd-studio/service-buses
```
-----

### Command Bus:
```php
<?php

use AwdStudio\ServiceBuses\CommandBus\CommandBus;

// Create any command you need
class MyCommand
{
public $value;
}

## Predefined buses:

// Handlers supposed to be invokable
class MyCommandHandler
{
// You can use types to define processing command
public function __invoke(MyCommand $command): void
{
// Process the command ...
}
}

$commandBus = new CommandBus($handlers, $middlewareChain);
$commandBus->handle(new MyCommand());
```

-----

### Query Bus:
### Command bus:
```php
<?php

use AwdStudio\ServiceBuses\QueryBus\QueryBus;
use AwdStudio\Bus\Handler\InMemoryHandlerLocator;
use AwdStudio\Bus\Middleware\MiddlewareChain;
use AwdStudio\Command\CommandBus;

// Create any query you need
class MyQuery
{
public $value;
class MyCommand {
// Messages might be any of PHP class.
// No any of implementation or extending required.
}

$handlers = new InMemoryHandlerLocator();
// Register a handler. It can be any callable thing.
$handlers->add(MyCommand::class, static function (MyCommand $command): void {});

// Handlers supposed to be invokable
class MyQueryHandler
{
// You can use types to define processing query
public function __invoke(MyQuery $query): array
{
// Process the query and return the result ...
}
}
$middleware = new InMemoryHandlerLocator();
// Register a middleware. It can be any callable thing as well.
// The only thing is that it gets a callback with next middleware as a 2nd param.
$middleware->add(MyCommand::class, static function (MyCommand $command, callable $next): void {
// Do whatever you need before the handler.
$next(); // Just dont forget to call a next callback.
// Or after...
});
$chain = new MiddlewareChain($middleware);

$queryBus = new QueryBus($handlers, $middlewareChain);
$result = $queryBus->handle(new MyQuery());
```

-----

### Event Bus:
```php
<?php

use AwdStudio\ServiceBuses\EventBus\EventBus;

// Create any event you need
class MyEvent
{
public $value;
}


// Handlers supposed to be invokable
class MyEventHandler
{
// You can use types to define processing event
public function __invoke(MyEvent $event): void
{
// Process the event ...
}
}
$bus = new CommandBus($handlers, $chain);

$eventBus = new EventBus($handlers, $middlewareChain);
$eventBus->handle(new MyEvent());
$bus->handle(new MyCommand());
```

-----
Expand Down
9 changes: 5 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
"description": "An implementation of such kind of patterns as: Command Bus, Query Bus and Event Bus; in a single package; driven by a Dependency Injection Container",
"type": "library",
"require": {
"php": ">=7.1",
"psr/container": "^1.0"
"php": ">=7.3",
"psr/container": "^1.0",
"phpspec/prophecy-phpunit": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^8.1",
"phpunit/php-code-coverage": "^7.0",
"phpunit/phpunit": "^9.0",
"phpunit/php-code-coverage": "^8.0",
"dg/bypass-finals": "^1.1",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
Expand Down
4 changes: 4 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ parameters:
level: max
paths:
- src

ignoreErrors:
- '#PHPDoc tag @template [a-zA-Z0-9]+ for [a-zA-Z0-9\\_\s]+ with bound type callable is not supported\.#'
- '#PHPDoc tag @param for parameter [a-zA-Z0-9\$\_]+ with type [a-zA-Z0-9]+ is not subtype of native type callable\.#'
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace AwdStudio\ServiceBuses\Exception;
namespace AwdStudio\Bus\Exception;

abstract class BusException extends \RuntimeException
{
Expand Down
9 changes: 9 additions & 0 deletions src/Bus/Exception/InvalidHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace AwdStudio\Bus\Exception;

final class InvalidHandler extends BusException
{
}
13 changes: 13 additions & 0 deletions src/Bus/Exception/NoHandlerDefined.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace AwdStudio\Bus\Exception;

final class NoHandlerDefined extends BusException
{
public function __construct(object $message)
{
parent::__construct(\sprintf('No handlers for a message "%s"', \get_class($message)), 1, null);
}
}
29 changes: 29 additions & 0 deletions src/Bus/Handler/HandlerRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace AwdStudio\Bus\Handler;

use AwdStudio\Bus\HandlerLocator;

/**
* @psalm-template TCallback of callable
* @phpstan-template TCallback of callable
*
* @extends HandlerLocator<TCallback>
*/
interface HandlerRegistry extends HandlerLocator
{
/**
* Registers a handler from a PSR-container as a message handler.
*
* @param string $messageId
* @param string $handlerId
*
* @throws \AwdStudio\Bus\Exception\InvalidHandler
*
* @psalm-param class-string $messageId
* @phpstan-param class-string $messageId
*/
public function register(string $messageId, string $handlerId): void;
}
54 changes: 54 additions & 0 deletions src/Bus/Handler/InMemoryHandlerLocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace AwdStudio\Bus\Handler;

use AwdStudio\Bus\HandlerLocator;

/**
* @psalm-external-mutation-free
*
* @implements HandlerLocator<callable(object $message, mixed ...$extraParams): mixed>
*/
final class InMemoryHandlerLocator implements HandlerLocator
{
/**
* @var array
*
* @psalm-var array<class-string, list<callable(object $message, mixed ...$extraParams): mixed>>
* @phpstan-var array<class-string, list<callable(object $message, mixed ...$extraParams): mixed>>
*/
private $handlers;

public function __construct()
{
$this->handlers = [];
}

/**
* {@inheritdoc}
*/
public function add(string $messageId, callable $handler): void
{
$this->handlers[$messageId][] = $handler;
}

/**
* {@inheritdoc}
*/
public function has(string $messageId): bool
{
return !empty($this->handlers[$messageId]);
}

/**
* {@inheritdoc}
*/
public function get(string $messageId): \Traversable
{
foreach ($this->handlers[$messageId] ?? [] as $handler) {
yield $handler;
}
}
}
Loading

0 comments on commit 01a45b4

Please sign in to comment.