MessageRouter
Pluggable inbound routing interface used by ReceiverActor to resolve each incoming Messenger envelope to the Nexus ActorRef that should receive its message.
What it does
MessageRouter is a single-method interface. ReceiverActor calls route() for every envelope it drains from the transport. Returning null marks the message unroutable and triggers the UnroutablePolicy configured on ReceiverActorConfig (reject or dead-letters).
Two concrete implementations ship with the package:
MapMessageRouter
Exact PHP message class → ActorRef lookup. This is the right choice for most applications where message types are known at startup.
/**
* @param Route<object> ...$routes
*/
public function __construct(Route ...$routes)
Routes are built through the typed Route::to() boundary, which checks at analysis time that each target ref handles its routed message class. route() returns the registered ref for $message::class or null if the class is not in the map. The lookup is an O(1) array fetch.
StampMessageRouter
Cluster seam: resolves the TargetActorPathStamp on the envelope against a path-keyed registry. Use this when a remote producer stamps the target actor path (e.g., when routing across cluster nodes). Messages without the stamp, or with a path not in the registry, are unroutable.
/**
* @param array<string, ActorRef<object>> $registry keyed by actor-path string
* @param TargetAuthorizer|null $authorizer authorizes producer → target routing per envelope
*/
public function __construct(private array $registry, private ?TargetAuthorizer $authorizer = null)
route() reads $envelope->last(TargetActorPathStamp::class) and looks up $registry[$stamp->path].
Authorizing producer → target routes
Because the target is selected by a producer-controlled stamp, a producer with publish rights could otherwise invoke any registered target and consume its capacity (SEC-012). Pass a TargetAuthorizer to gate this: a resolved target is only returned if the authorizer permits the envelope's producer to reach it. A denied envelope is unroutable — the ReceiverActor rejects or dead-letters it per its policy — so an unauthorized producer never reaches the target actor.
MapTargetAuthorizer is a static allowlist mapping a producer identity (read from the ProducerIdentityStamp, wire header X-Nexus-Producer-Identity) to the exact target paths it may reach. It fails closed: no identity stamp, an unknown identity, or a target outside that identity's list is denied (and logged when a PSR-3 logger is supplied).
$router = new StampMessageRouter(
['/user/orders' => $ordersRef, '/user/payments' => $paymentsRef],
new MapTargetAuthorizer([
'orders-svc' => ['/user/orders'],
'billing-svc' => ['/user/payments'],
]),
);
A ProducerIdentityStamp proves origin only as far as the producer is trusted. Across mutually untrusted producers the identity must be established or validated at a trusted boundary — an authenticated transport, a broker ACL that stamps identity, or a signed envelope — otherwise a producer can assert any identity. The authorizer enforces the ACL; the trust in the identity comes from the boundary that set it. Pair it with broker-side ACLs when producers are not mutually trusted.
Omitting the authorizer preserves the previous behavior (any producer may reach any registered target), so this is a backward-compatible, opt-in tightening.
Interface
interface MessageRouter
{
/**
* @return ActorRef<object>|null null means unroutable
*/
public function route(object $message, Envelope $envelope): ?ActorRef;
}
Example
use Monadial\Nexus\Messenger\Routing\MapMessageRouter;
use Monadial\Nexus\Messenger\Routing\Route;
use Monadial\Nexus\Messenger\Routing\StampMessageRouter;
// Type-based routing (most common)
$router = new MapMessageRouter(
Route::to(OrderPlaced::class, $ordersActor),
Route::to(PaymentMade::class, $paymentsActor),
);
// Path-stamp routing (cluster seam)
$router = new StampMessageRouter([
'/user/orders' => $ordersActor,
'/user/payments' => $paymentsActor,
]);
// Custom router: route on any envelope property
$router = new class implements MessageRouter {
public function route(object $message, Envelope $envelope): ?ActorRef {
// inspect stamps, message fields, etc.
return $message instanceof PriorityMessage
? $this->priorityRef
: $this->defaultRef;
}
};
Full API reference
MessageRouter interface · MapMessageRouter · StampMessageRouter
See also
- nexus-messenger package — bridge overview and full wiring guide
- Scaling & Clustering — cluster package that uses
StampMessageRouteras a seam - ReceiverActor — the consumer that calls
route()on every envelope - Messenger bridge guide — end-to-end routing examples