Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions docs/1_callable_collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,32 @@ A callable with a higher priority will be executed before a callable with a lowe

## Executing a collection of callables

The `Sirius\Invokator` library comes with a few **collection processors** which are act as collection registries/repositories and collection executors.
A `CallableCollection` is the underlying queue. To actually execute the callables you build one of the **runnable callable stacks** that come with `Sirius\Invokator` (here a `CallablePipeline`) and add the callables to it. Each runner owns its own collection and is executed with a single `run(...)` call.

```php
use Sirius\Invokator\Processors\PipelineProcessor;
use Sirius\Invokator\Callables\CallablePipeline;
use Sirius\Invokator\Invoker;

// this is required for callables like "SomeClass@someMethod"
// and by callables that have dependencies
$invoker = new Invoker($yourChoiceOfDependencyInjectionContainer);
$processor = new PipelineProcessor($invoker);

// execute the collection created above as a pipeline with one parameter
$processor->processCollection($callables, ' world ');
$pipeline = new CallablePipeline($invoker);
$pipeline->add('trim')
->add(fn ($s) => 'hello ' . $s)
->add('Str::toUpper')
->add('SlackChannel@send', -100);

// execute the pipeline with one parameter
$pipeline->run(' world ');

// this will
// 1. trim the parameter => `world`
// 2. concatenate with "hello " => `hello world`
// 3. make the string uppercase => `HELLO WORLD`,
// 4. write the string as an info message to the logger
// 5. send the string to a SlackChannel
// 4. send the string to a SlackChannel
```

Each type of callables processor has its own quirks that you can learn on the next page.
Each type of callable runner has its own quirks that you can learn on the next page.

[Next: The callable_processors](2_callable_processors.md)
31 changes: 23 additions & 8 deletions docs/2_1_simple_collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,41 @@
title: Simple callable collections
---

# The simple processor
# The simple collection behaviour

This processor has the following characteristics:
The "simple" behaviour has the following characteristics:
1. All the parameters are passed down to each of the callables. This means all the callables should have the same signature (although this restriction can be by-passed with **modifiers**)
2. The values returned by the callables are ignored

There is no longer a dedicated `SimpleCallablesProcessor`. This behaviour is now provided by `CallableAction` with `argumentsLimit: null`, which passes **every** argument unchanged to each callable.

#### Use case: Reporting/logging

Using the `Invokator` registry:

```php
$invokator->action('log')
->add('FileLogger@log', 0, null) // priority, then argumentsLimit: null
->add('SlackNotification@send', 0, null)
->add('TextNotification@send', 0, null);

$invokator->action('log')->run($severity, $message, $context);
```

or standalone:

```php
use Sirius\Invokator\Invoker;
use Sirius\Invokator\Processors\SimpleCallablesProcessor;
use Sirius\Invokator\Callables\CallableAction;

$invoker = new Invoker($psr11Container);
$processor = new SimpleCallablesProcessor($invoker);

$processor->add('log', 'FileLogger@log') // this returns the Stack
->add('SlackNotification@send')
->add('TextNotification@send');
$action = new CallableAction($invoker);
$action->add('FileLogger@log', 0, null)
->add('SlackNotification@send', 0, null)
->add('TextNotification@send', 0, null);

$processor->process('log', $severity, $message, $context);
$action->run($severity, $message, $context);
```

[Next: Pipelines](2_2_pipelines.md)
20 changes: 7 additions & 13 deletions docs/2_2_pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,20 @@ title: Pipelines

# Pipelines

This processor has the following characteristics:
A pipeline has the following characteristics:
1. The parameters are passed the first callable
2. The value returned by each callable is the first and only parameter passed to the next callable
3. All the callables are called in sequence

#### Use case

```php
use Sirius\Invokator\Invoker;
use Sirius\Invokator\Processors\PipelineProcessor;

$invoker = new Invoker($psr11Container);
$processor = new PipelineProcessor($invoker);

$processor->get('tax_report')
$invokator->pipeline('tax_report')
->add('ImportCsv@taxReport') // this receives a DTO with a file and a user ID, imports it into a table and returns a DTO with the table name and user ID
->add('GenerateTaxReport@compileExcelFile') // this receives the DTO returned by the previous callable, returns a DTO with the name of the XLS file and user ID
->add('NotifyReportReady@notifyTaxReport'); // this receives the DTO from the previous callable and sends an email

$processor->process('tax_report', new TaxReportDTO('path_to_csv_file', 'user_id') );
$invokator->pipeline('tax_report')->run(new TaxReportDTO('path_to_csv_file', 'user_id'));
```

The example above uses DTOs to pass messages from one step to the next because each callable in the pipeline receives only one argument and most likely there is some "global" data that each step should know about (eg: the client ID for that report)
Expand All @@ -33,13 +27,13 @@ In some situations pipelines can be interrupted. There are 2 scenarios for this

###### 1. The callable that is executed knows it can be retried in the future

In this case it can return an instance of `SuggestedRetry` which instructs the pipeline processor to not execute the rest of the callables and return a `PipelinePromise()`
In this case it can return an instance of `SuggestedRetry` which instructs the pipeline to not execute the rest of the callables and return a `PipelinePromise()`

```php
$processor->get('sales_report')
$invokator->pipeline('sales_report')
->add('SalesReport@prepareData')
->add('SalesReport@generateReport')
->add('Notification@sendSalesReport')
->add('Notification@sendSalesReport');
```

In this case the `SalesReport@generateReport` talks to a 3rd-party API which returns `429 Too Many Requests`.
Expand All @@ -48,7 +42,7 @@ Sure, the same can be achieved by throwing an exception and re-trying the entire

###### 2. The callable that is executed knows the next step should be delayed

In this case it can return an instance of `SuggestedResume` which instructs the pipeline processor to not execute the rest of the callables and return a `PipelinePromise()`
In this case it can return an instance of `SuggestedResume` which instructs the pipeline to not execute the rest of the callables and return a `PipelinePromise()`

The `PipelinePromise` object has a few properties:
- the remaining callables in the pipeline
Expand Down
20 changes: 17 additions & 3 deletions docs/2_3_command_bus.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: PSR-14 Command bus implementation
title: Command bus implementation
---

# Command bus implementation
Expand All @@ -14,11 +14,11 @@ The implementation of this pattern in the `Sirius\Invokator` library has the fol

1. The `PurchaseProductCommand` command class is automatically linked to the `PurchaseProductHandler` in the same namespace. This happens unless you specify a handler via the `register()` method
2. The handler class has to implement the method `handle($command)` or `__invoke($command)`
3. The processing of the command can be extended via middlewares (the command bus is a special type of [middleware processor](2_3_middlewares.md))
3. The processing of the command can be extended via middlewares — under the hood the bus routes the command through a [`CallableMiddleware`](2_3_middlewares.md) stack to the handler

```php
use Sirius\Invokator\Invoker;
use Sirius\Invokator\Processors\CommandBus;
use Sirius\Invokator\Callables\CommandBus;

$invoker = new Invoker($psr11Container);
$bus = new CommandBus($invoker);
Expand All @@ -40,6 +40,20 @@ You can add middlewares at any point in time, before or after registering the co

```php
$bus->addMiddleware(CreateProductCommand::class, 'CommandMiddleware@execute', 100 /* priority (optional) */);

$bus->handle(new CreateProductCommand(/* ... */));
```

#### Through the Invokator registry

The same can be expressed with the `Sirius\Invokator\Invokator` class. `command()` returns a `CallableCommand` on which you add middleware, optionally set the handler, and run the command:

```php
$invokator->command(CreateProductCommand::class)
->add('CommandMiddleware@execute', 100)
->handledBy(CreateProductHandler::class);

$invokator->handle(new CreateProductCommand(/* ... */));
```

[Next: Actions a la Wordpress](2_4_wordpress_actions.md)
15 changes: 15 additions & 0 deletions docs/2_3_event_dispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ $dispatcher->subscribeOnceTo(Event::class, 'some_callable', 0);
$dispatcher->dispatch(new Event());
```

### Through the Invokator registry

If you use the `Sirius\Invokator\Invokator` class, `event()` returns a `CallableEvent` that wraps the dispatcher and is the unified way to subscribe and dispatch:

```php
// subscribe a listener (optionally a priority)
$invokator->event(Event::class)->add('some_callable');
// subscribe a listener that runs only once
$invokator->event(Event::class)->once('some_callable');

// dispatch the event (these are equivalent)
$invokator->dispatch(new Event());
$invokator->event(Event::class)->run(new Event());
```

### Named events

If you want to identify the events by something other than the class name you can make the event classes implement the `HasEventname` interface
Expand Down
12 changes: 3 additions & 9 deletions docs/2_3_middlewares.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,22 @@ title: Middlewares

# Middlewares

This processor has the following characteristics:
A middleware stack has the following characteristics:
1. All the parameters are passed down to each of the callables as which means all the callables should have the same signature (although this restriction can be by-passed with **modifiers**)
2. The second to the last callables receive a `$next` as their last parameter which is a callable that continues the calls from the collection
3. Each callable may call `$next` or not

#### Use case

```php
use Sirius\Invokator\Invoker;
use Sirius\Invokator\Processors\MiddlewareProcessor;

$invoker = new Invoker($psr11Container);
$processor = new MiddlewareProcessor($invoker);

$processor->get('http_handler')
$invokator->middleware('http_handler')
->add('CsrfCheckMiddleware')
->add('TrimStringsMiddleware')
->add('AuthMiddleware')
->add('CacheMiddleware')
->add('RouterMiddleware');

$processor->process('http_handler', new HttpRequest);
$invokator->middleware('http_handler')->run(new HttpRequest);
```

While this example is for HTTP middleware, it does not implement the [PSR-15 middleware specifications](https://www.php-fig.org/psr/psr-15/) as it does not enforce their respective signatures. It would be up to your app to enforce those restrictions
Expand Down
25 changes: 8 additions & 17 deletions docs/2_4_wordpress_actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,21 @@ title: Actions a la Wordpress

# Actions (a la Wordpress)

This processor is similar to the "Simple callables processor" with the difference that you also have to specify a limit for the arguments passed to each callable.
A `CallableAction` is similar to the simple collection behaviour with the difference that you also specify, per callable, a limit for the arguments passed to it (the default being `1`, the Wordpress action convention).

This means that the callables do not have to have the same signature as for the SimpleCallables processor. This processor is just a convenience as the same result could be been achieved using the ['limit_arguments' modifier](3_callable_modifiers.md)
This means that the callables do not have to have the same signature. It is just a convenience as the same result could be been achieved using the ['limit_arguments' modifier](3_callable_modifiers.md). Passing `argumentsLimit: null` passes every argument unchanged (the old "simple collection" behaviour).

#### Use case

```php
use Sirius\Invokator\Invoker;
use Sirius\Invokator\Processors\ActionsProcessor;
$invokator->action('save_post')
->add('validate_taxonomies', 0, 2) // callback, priority, number of arguments passed
->add('validate_acf_fields', 1, 2)
->add('check_permissions', 10, 1);

$invoker = new Invoker($psr11Container);
$processor = new ActionsProcessor($invoker);

$processor->add('save_post', 'validate_taxonomies', 0, 2); // callback, priority, number of arguments passed
$processor->add('save_post', 'validate_acf_fields', 1, 2);
$processor->add('save_post', 'check_permissions', 10, 1);

$processor->process('save_post', $postID, $wpPost, $update);
$invokator->action('save_post')->run($postID, $wpPost, $update);
```

**Attention!** The processor's `get()` and `add()` method return the callables collection, so you can't chain callables with arguments limit. For example the code below doesn't work as you might expect
```php
$processor->add('save_post', 'validate_taxonomies', 0, 2) // this returns the callables collection
->add('validate_acf_fields', 0, 2); // this won't place a limit on the arguments for the 'validate_acf_fields' function since the callables is returned by the first add() call
```
Chaining works as expected: `add()` returns the action itself and each call keeps its own argument limit, so the example above limits `validate_taxonomies` and `validate_acf_fields` to 2 arguments and `check_permissions` to 1.

[Next: Filters a la Wordpress](2_5_wordpress_filters.md)
26 changes: 8 additions & 18 deletions docs/2_5_wordpress_filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,20 @@ title: Filters a la Wordpress

# Filters (a la Wordpress)

This processor is similar to the "Pipeline processor" with the difference that the additional parameters passed to the `process()` method are also passed to the other callbacks.
A `CallableFilter` is similar to a pipeline with the difference that only the **first** argument is threaded through the callables while the additional parameters passed to `run()` are also passed (as context) to each callback.

Just like the "actions processor" you have to specify the number of arguments passed to the callbacks
Just like the action runner you specify, per callable, the number of arguments passed to it (the default being `1`).

#### Use case

```php
use Sirius\Invokator\Invoker;
use Sirius\Invokator\Processors\FiltersProcessor;
$invokator->filter('the_title')
->add('add_category_name', 0, 2) // callback, priority, no of arguments passed
->add('add_site_name', 0, 2);

$invoker = new Invoker($psr11Container);
$processor = new FiltersProcessor($invoker);

$processor->add('the_title', 'add_category_name', 0, 2); // callback, priority, no of arguments passed
$processor->add('the_title', 'add_site_name', 0, 2);

$processor->process('the_title', $postTitle, $postID);
$invokator->filter('the_title')->run($postTitle, $postID);
```

**Attention!** The processor's `get()` and `add()` method return the Stack object, so you can't chain callables with arguments limit. For example the code below doesn't work as you might expect

```php
$processor->add('the_title', 'add_category_name', 0, 2) // this returns the collection
->add('add_site_name', 0, 2) // this won't place a limit on the arguments for the 'add_site_name' function since the callables is returned by the first add() call
```
Chaining works as expected: `add()` returns the filter itself and each call keeps its own argument limit, so both `add_category_name` and `add_site_name` above receive 2 arguments.

[Next: Custom callable processors](2_6_custom_processors.md)
[Next: Custom callable stacks](2_6_custom_processors.md)
29 changes: 23 additions & 6 deletions docs/2_6_custom_processors.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
---
title: Custom callable processors
title: Custom callable stacks
---

# Custom callables processor
# Custom callable stacks

You can easily build your own custom processor by extending the `SimpleCallablesProcessor` or starting from scratch as the API for a callables processor is very simple.
You can easily build your own runner by extending `Sirius\Invokator\Callables\AbstractCallableStack` and implementing the `run(mixed ...$args): mixed` method. The base class gives you the `add()` method, the `$this->invoker` used to execute callables, and a protected `freshStack()` helper that returns a disposable clone of the registered callables (a `CallableCollection` you can drain with `extract()` without altering the runner).

If you extend the `SimpleCallablesProcessor` you only need to implement the `processCollection()` method.
```php
use Sirius\Invokator\Callables\AbstractCallableStack;

class MyRunner extends AbstractCallableStack
{
public function run(mixed ...$args): mixed
{
$stack = $this->freshStack();
$result = null;
while (! $stack->isEmpty()) {
$callable = $stack->extract();
$result = $this->invoker->invoke($callable, ...$args);
}

return $result;
}
}
```

Here are some ideas:
1. pipelines where all the callbacks receive the same arguments and where the result of a callback becomes the first argument in the list. It would be similar to the "Filters processor" but without having to specify the limit for the arguments.
2. HTTP middleware implementation of the PSR-15 standard. It would be similar to the "Middlewares processor" but with the restriction that all the callables should have the same signature.
1. A pipeline where all the callbacks receive the same arguments and where the result of a callback becomes the first argument in the list. It would be similar to the `CallableFilter` but without having to specify the argument limit.
2. An HTTP middleware implementation of the PSR-15 standard. It would be similar to the `CallableMiddleware` but with the restriction that all the callables share the same signature.

[Next: callable modifiers](3_callable_modifiers.md)
Loading
Loading