Middleware is a feature for adding hooks before and after processing an actor’s messages.
You can add middleware to both received and sent messages.
In Phluxor, these are called ReceiverMiddleware and SenderMiddleware,
and you can add middleware using Phluxor\ActorSystem\Props
.
Middleware for received messages adds hooks before and after an actor receives a message.
You can add middleware to an actor’s received messages using the Props::withReceiverMiddleware
method.
You can add any number of middlewares, which will be executed in the order they were added.
The Props::withReceiverMiddleware
method requires a class that implements Phluxor\ActorSystem\Props\ReceiverMiddlewareInterface
,
and that class must implement an __invoke
method, which returns Phluxor\ActorSystem\Message\ReceiverFunctionInterface
.
<?php
declare(strict_types=1);
namespace Phluxor\ActorSystem\Props;
use Closure;
use Phluxor\ActorSystem\Context\ContextInterface;
use Phluxor\ActorSystem\Context\ReceiverInterface;
use Phluxor\ActorSystem\Message\MessageEnvelope;
use Phluxor\ActorSystem\Message\ReceiverFunctionInterface;
interface ReceiverMiddlewareInterface
{
/**
* @param Closure(ReceiverInterface|ContextInterface, MessageEnvelope): void|ReceiverFunctionInterface $next
* @return ReceiverFunctionInterface
*/
public function __invoke(
Closure|ReceiverFunctionInterface $next
): ReceiverFunctionInterface;
}
ReceiverFunctionInterface is a function that is executed when an actor receives a message.
Use the $next
argument to call the next middleware.
<?php
declare(strict_types=1);
namespace Phluxor\ActorSystem\Message;
use Phluxor\ActorSystem\Context\ContextInterface;
use Phluxor\ActorSystem\Context\ReceiverInterface;
interface ReceiverFunctionInterface
{
/**
* @param ReceiverInterface|ContextInterface $context
* @param MessageEnvelope $messageEnvelope
* @return void
*/
public function __invoke(
ReceiverInterface|ContextInterface $context,
MessageEnvelope $messageEnvelope
): void;
}
ReceiverFunctionInterface
is a function that is executed when an actor receives a message.
$context
represents the actor’s context, and $messageEnvelope
represents the received message.
This allows you to customize the processing for messages received by the actor.
An implementation example is as follows:
<?php
declare(strict_types=1);
namespace Example\Middleware;
use Closure;
use Phluxor\ActorSystem\Context\ContextInterface;
use Phluxor\ActorSystem\Context\ReceiverInterface;
use Phluxor\ActorSystem\Message\MessageEnvelope;
use Phluxor\ActorSystem\Message\ReceiverFunctionInterface;
readonly class ReceiverFactory implements ReceiverFunctionInterface
{
public function __construct(
private Closure|ReceiverFunctionInterface $next
) {
}
public function __invoke(
ContextInterface|ReceiverInterface $context,
MessageEnvelope $messageEnvelope
): void {
var_dump('before receiver');
$next = $this->next;
$next($context, $messageEnvelope); // next middleware or actor receive
var_dump('after receiver');
}
}
In the example above, logs are output before and after the actor receives a message.
The class for adding middleware would look like this:
<?php
declare(strict_types=1);
namespace Example\Middleware;
use Closure;
use Phluxor\ActorSystem\Message\ReceiverFunctionInterface;
use Phluxor\ActorSystem\Props\ReceiverMiddlewareInterface;
class ReceiverMiddleware implements ReceiverMiddlewareInterface
{
public function __invoke(ReceiverFunctionInterface|Closure $next): ReceiverFunctionInterface
{
return new ReceiverFactory($next);
}
}
To apply these to an actor, use the Props::withReceiverMiddleware
method.
<?php
declare(strict_types=1);
namespace Example;
use Example\Message\Hello;
use Example\Middleware\ReceiverMiddleware;
use Phluxor\ActorSystem\Context\ContextInterface;
use Phluxor\ActorSystem\Message\ActorInterface;
use Phluxor\ActorSystem\Props;
class ParentActor implements ActorInterface
{
public function receive(ContextInterface $context): void
{
$message = $context->message();
switch (true) {
case $message instanceof Hello:
$ref = $context->spawn(
Props::fromProducer(
fn() => new ChildActor(),
Props::withReceiverMiddleware(
new ReceiverMiddleware()
)
)
);
$context->send($ref, $message);
break;
}
}
}
When multiple middlewares are added, they are executed as shown below.
string(15) "before receiver" // first receiver middleware
string(16) "before receiver2" // second receiver middleware
string(15) "after receiver2" // second receiver middleware
string(14) "after receiver" // first receiver middleware
Middleware for sent messages adds hooks before and after an actor sends a message.
You can add middleware to an actor’s sent messages using the Props::withSenderMiddleware
method.
As with ReceiverMiddleware
, you can add any number of middlewares,
and they will be executed in the order they were added.
The Props::withSenderMiddleware
method requires a class that implements Phluxor\ActorSystem\Props\SenderMiddlewareInterface
,
and that class must implement an __invoke
method that returns Phluxor\ActorSystem\Message\SenderFunctionInterface
.
<?php
declare(strict_types=1);
namespace Phluxor\ActorSystem\Props;
use Closure;
use Phluxor\ActorSystem\Context\ContextInterface;
use Phluxor\ActorSystem\Context\SenderInterface;
use Phluxor\ActorSystem\Message\MessageEnvelope;
use Phluxor\ActorSystem\Message\SenderFunctionInterface;
use Phluxor\ActorSystem\Ref;
interface SenderMiddlewareInterface
{
/**
* @param Closure(SenderInterface|ContextInterface, Ref, MessageEnvelope): void|SenderFunctionInterface $next
* @return SenderFunctionInterface
*/
public function __invoke(Closure|SenderFunctionInterface $next): SenderFunctionInterface;
}
SenderFunctionInterface
is a function that is executed when an actor sends a message.
Use the $next
argument to call the next middleware.
<?php
declare(strict_types=1);
namespace Phluxor\ActorSystem\Message;
use Phluxor\ActorSystem\Context\SenderInterface;
use Phluxor\ActorSystem\Ref;
interface SenderFunctionInterface
{
/**
* @param SenderInterface $context
* @param Ref|null $target
* @param MessageEnvelope $messageEnvelope
* @return void
*/
public function __invoke(
SenderInterface $context,
Ref|null $target,
MessageEnvelope $messageEnvelope
): void;
}
SenderFunctionInterface
is a function that is executed when an actor sends a message.
$context
represents the actor’s context, $target
represents the target actor,
and $messageEnvelope
represents the message being sent.
This allows you to customize the processing for messages sent by the actor.
The basic implementation method is similar to that of middleware for received messages.
An example is shown below:
<?php
declare(strict_types=1);
namespace Example\Middleware;
use Closure;
use Phluxor\ActorSystem\Context\SenderInterface;
use Phluxor\ActorSystem\Message\MessageEnvelope;
use Phluxor\ActorSystem\Message\SenderFunctionInterface;
use Phluxor\ActorSystem\Ref;
readonly class SenderFactory implements SenderFunctionInterface
{
public function __construct(
private SenderFunctionInterface|Closure $next
) {
}
public function __invoke(SenderInterface $context, ?Ref $target, MessageEnvelope $messageEnvelope): void
{
var_dump('before sender');
$next = $this->next;
$next($context, $target, $messageEnvelope);
var_dump('after sender');
}
}
The class for adding middleware would look like this:
<?php
declare(strict_types=1);
namespace Example\Middleware;
use Closure;
use Phluxor\ActorSystem\Message\SenderFunctionInterface;
use Phluxor\ActorSystem\Props\SenderMiddlewareInterface;
class SenderMiddleware implements SenderMiddlewareInterface
{
public function __invoke(SenderFunctionInterface|Closure $next): SenderFunctionInterface
{
return new SenderFactory($next);
}
}
To apply these to an actor, use the Props::withSenderMiddleware
method.
<?php
use Phluxor\ActorSystem;
$system = ActorSystem::create();
$ref = $system->root()->spawn(
ActorSystem\Props::fromProducer(
fn() => new Example\ParentActor(),
ActorSystem\Props::withSenderMiddleware(
new Example\Middleware\SenderMiddleware()
)
)
);
The usage within the actor context is the same.
Of course, you can also add multiple middlewares.
Middleware is a feature for adding hooks before and after processing an actor’s messages,
making it useful for implementing Event Sourcing or CQRS as well.
For example, you can implement message persistence as middleware that issues events before and after an actor receives a message.
If you would like to learn more about these mechanisms,
please refer to the sections on persistence and event sourcing (documentation in preparation).
MailboxMiddleware is a feature that allows you to add hooks when an actor’s message arrives in the mailbox.
Similar to other middleware, MailboxMiddleware can be added using Phluxor\ActorSystem\Props
.
To use MailboxMiddleware,
you need to provide a class that implements Phluxor\ActorSystem\Mailbox\MailboxMiddlewareInterface
,
and use the Props::withMailboxProducer
method to add the MailboxMiddleware.
<?php
declare(strict_types=1);
namespace Phluxor\ActorSystem\Mailbox;
interface MailboxMiddlewareInterface
{
public function mailboxStared(): void;
public function messagePosted(mixed $message): void;
public function messageReceived(mixed $message): void;
public function mailboxEmpty(): void;
}
The mailboxStarted
method is called when the Mailbox itself starts.
The messagePosted
method is called when a system message or user message is sent to the Mailbox.
After being called, the message is pushed to the Mailbox.
The messageReceived
method is called when a system message or user message reaches or is received by the Mailbox.
The mailboxEmpty
method is called each time the Mailbox becomes empty.
Customizing the Mailbox requires a basic understanding of Phluxor,
so unless you have a solid understanding of the actor model and Phluxor,
customization is not recommended.
However, adding middleware is straightforward.
By default, Phluxor uses an Unbounded Mailbox. To add middleware to this Mailbox, you can do so as follows:
<?php
declare(strict_types=1);
namespace Example;
use Example\Message\Hello;
use Example\Middleware\MailboxMiddleware;
use Phluxor\ActorSystem\Context\ContextInterface;
use Phluxor\ActorSystem\Mailbox\Unbounded;
use Phluxor\ActorSystem\Message\ActorInterface;
use Phluxor\ActorSystem\Props;
class ParentActor implements ActorInterface
{
public function receive(ContextInterface $context): void
{
$message = $context->message();
switch (true) {
case $message instanceof Hello:
$ref = $context->spawn(
Props::fromProducer(
fn() => new ChildActor(),
Props::withMailboxProducer(
new Unbounded(new MailboxMiddleware())
)
)
);
$context->send($ref, $message);
break;
}
}
}