Skip to main content

Fan-Out Processing

Sometimes a single event needs to reach more than one destination — for example, a file log and a future Redis or database processor. CompositeSystemEventProcessor fans a single event out to any number of configured SystemEventProcessorInterface destinations, without SystemEventsServiceProvider needing to know about the fan-out at all.


Basic Usage

use DomainFlow\SystemEvents\Processor\CompositeSystemEventProcessor;
use DomainFlow\SystemEvents\Processor\FileSystemEventProcessor;

$processor = new CompositeSystemEventProcessor(
new FileSystemEventProcessor(),
new SomeOtherProcessor(),
);

$processor->processEvent('payment.charged', ['amount' => 4200]);

Every configured processor implements SystemEventProcessorInterface, so CompositeSystemEventProcessor itself also implements it — it's a drop-in replacement anywhere a single processor is expected.


Guarantees

  • Every processor always runs. All configured processors receive the event, in construction order, even if an earlier processor fails. One destination's outage must never silently prevent a later, healthy destination from receiving the event.
  • Failures are aggregated, not swallowed. If one or more processors fail, every failure is collected and thrown together — once, after all processors have run — as a single CompositeProcessingException.

Handling Failures: CompositeProcessingException

final class CompositeProcessingException extends RuntimeException
{
public readonly string $eventName;

/** @var list<Throwable> */
public readonly array $failures;
}
PropertyTypeDescription
$eventNamestringThe event that was being processed when one or more processors failed.
$failureslist<Throwable>Every exception thrown, in the order the failing processors were invoked.

getPrevious() is set to the first failure in $failures, so standard exception-chain tooling (log formatters, error trackers) still surfaces a root cause even without inspecting $failures directly.

use DomainFlow\SystemEvents\Exception\CompositeProcessingException;

try {
$processor->processEvent('payment.charged', ['amount' => 4200]);
} catch (CompositeProcessingException $e) {
foreach ($e->failures as $failure) {
error_log("Destination failed for {$e->eventName}: {$failure->getMessage()}");
}
}

Using It as the Provider's Processor

Bind CompositeSystemEventProcessor as SystemEventProcessorInterface before the provider's boot() runs, so SystemEventsServiceProvider picks it up as the single processor it forwards every event to:

use DomainFlow\SystemEvents\Interface\SystemEventProcessorInterface;
use DomainFlow\SystemEvents\Processor\CompositeSystemEventProcessor;
use DomainFlow\SystemEvents\Processor\FileSystemEventProcessor;
use DomainFlow\SystemEvents\Provider\SystemEventsServiceProvider;

$app->bind(SystemEventProcessorInterface::class, function () {
return new CompositeSystemEventProcessor(
new FileSystemEventProcessor(),
new SomeOtherProcessor(),
);
}, true);

$app->registerProvider(new SystemEventsServiceProvider());

When used this way, a CompositeProcessingException is just another processEvent() failure from the provider's point of view — it's caught by the provider's own processor-failure isolation like any other exception, so it never propagates into the code that fired the original event. Your onProcessingFailure hook (if configured) receives the CompositeProcessingException itself, from which you can still read $failures to see exactly which destinations failed.