<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
different-universe / laravel-cqbus-mediator example snippets
use App\Mediator\Orders\CreateOrder;
use DifferentUniverse\CqbusMediator\Contracts\Mediator;
final class OrderController
{
public function __construct(
private readonly Mediator $mediator,
) {
}
public function store(StoreOrderRequest $request): JsonResponse
{
$result = $this->mediator->send(new CreateOrder(
userId: $request->user()->id,
items: $request->validated('items'),
));
return response()->json($result, 201);
}
}
return [
/*
|--------------------------------------------------------------------------
| Discovery Paths
|--------------------------------------------------------------------------
|
| These paths are scanned for mediator handlers when the discovery data
| provider builds the command, query, and internal handler maps. You may
| add any application or package paths that contain handler attributes.
|
*/
'discovery_paths' => [
app_path(),
],
/*
|--------------------------------------------------------------------------
| Cache File Path
|--------------------------------------------------------------------------
|
| This file stores the compiled mediator handler map generated by the
| cache command. The default location follows Laravel's convention for
| framework bootstrap cache files.
|
*/
'cache_file_path' => base_path('bootstrap/cache/mediator.php'),
/*
|--------------------------------------------------------------------------
| Global Pipelines
|--------------------------------------------------------------------------
|
| These pipeline classes are applied to every discovered command and query
| handler before any handler-specific pipelines. Individual handlers may
| opt out with the available pipeline exclusion attributes.
|
*/
'global_pipelines' => [],
/*
|--------------------------------------------------------------------------
| Command Pipelines
|--------------------------------------------------------------------------
|
| These pipeline classes are applied only to command handlers. They are
| appended after the global pipelines and before any pipelines declared
| directly on a command handler.
|
*/
'command_pipelines' => [],
/*
|--------------------------------------------------------------------------
| Query Pipelines
|--------------------------------------------------------------------------
|
| These pipeline classes are applied only to query handlers. They are
| appended after the global pipelines and before any pipelines declared
| directly on a query handler.
|
*/
'query_pipelines' => [],
/*
|--------------------------------------------------------------------------
| Generated Handler Namespace
|--------------------------------------------------------------------------
|
| This namespace is used by the mediator generator commands as the default
| location for new command, query, and internal handler classes. Relative
| namespaces are resolved beneath the application's root namespace.
|
*/
'root_namespace' => 'App\\Mediator',
/*
|--------------------------------------------------------------------------
| Generated Handler Suffix
|--------------------------------------------------------------------------
|
| This suffix is appended by the mediator generator commands when a new
| handler class name does not already end with it. Set this value to an
| empty string if you prefer to provide full class names manually.
|
*/
'handler_suffix' => 'Handler',
];
namespace App\Mediator\Orders;
final readonly class CreateOrder
{
public function __construct(
public int $userId,
public array $items,
) {
}
}
namespace App\Mediator\Orders;
use App\Models\Order;
use DifferentUniverse\CqbusMediator\Attributes\Handlers\CommandHandler;
#[CommandHandler]
final class CreateOrderHandler
{
public function handle(CreateOrder $command): Order
{
return Order::create([
'user_id' => $command->userId,
'items' => $command->items,
]);
}
}
use App\Mediator\Orders\CreateOrder;
use DifferentUniverse\CqbusMediator\Contracts\Mediator;
final class CheckoutController
{
public function __construct(
private readonly Mediator $mediator,
) {
}
public function __invoke(): JsonResponse
{
$order = $this->mediator->send(new CreateOrder(
userId: 1,
items: [['sku' => 'book', 'quantity' => 1]],
));
return response()->json($order, 201);
}
}
use App\Mediator\Orders\CreateOrder;
use DifferentUniverse\CqbusMediator\Facades\Mediator;
$order = Mediator::send(new CreateOrder(1, [
['sku' => 'book', 'quantity' => 1],
]));
use DifferentUniverse\CqbusMediator\Attributes\Handlers\CommandHandler;
#[CommandHandler]
final class CapturePaymentHandler
{
public function __construct(
private PaymentGateway $payments,
) {
}
public function handle(CapturePayment $command): Receipt
{
return $this->payments->capture($command->paymentId);
}
}
use DifferentUniverse\CqbusMediator\Attributes\Handlers\QueryHandler;
#[QueryHandler]
final class LookupOrderHandler
{
public function __construct(
private OrderRepository $orders,
) {
}
public function handle(LookupOrder $query): ?Order
{
return $this->orders->find($query->orderNumber);
}
}
#[CommandHandler]
final class CreateOrderHandler
{
public function handle(CreateOrder $command): Order
{
// ...
}
}
#[CommandHandler]
final class UpsertPostHandler
{
public function handle(CreatePost|UpdatePost $message): Post
{
// ...
}
}
#[CommandHandler('orders.create')]
final class CreateOrderHandler
{
public function handle(array $payload): Order
{
// ...
}
}
use DifferentUniverse\CqbusMediator\Facades\Mediator;
Mediator::send(new LookupOrder('ORD-100'));
Mediator::sendToChannel('orders.lookup', ['number' => 'ORD-100']);
use DifferentUniverse\CqbusMediator\Attributes\Handlers\CommandHandler;
#[CommandHandler(outputChannel: 'audit.store')]
final class CreateOrderHandler
{
public function handle(CreateOrder $command): array
{
return [
'order_id' => 123,
'status' => 'created',
];
}
}
use DifferentUniverse\CqbusMediator\Attributes\Handlers\InternalHandler;
#[InternalHandler('audit.store', outputChannel: 'response.enrich')]
final class StoreAuditTrail
{
public function handle(array $data): array
{
AuditTrail::create($data);
return $data;
}
}
#[InternalHandler('response.enrich')]
final class EnrichResponse
{
public function handle(array $data): array
{
$data['processed_at'] = now()->toISOString();
return $data;
}
}
namespace App\Mediator\Pipelines;
use Closure;
use Illuminate\Support\Facades\DB;
final class TransactionPipeline
{
public function handle(mixed $payload, Closure $next): mixed
{
return DB::transaction(fn () => $next($payload));
}
}
// config/mediator.php
use App\Mediator\Pipelines\LoggingPipeline;
use App\Mediator\Pipelines\QueryCachePipeline;
use App\Mediator\Pipelines\TransactionPipeline;
'global_pipelines' => [
LoggingPipeline::class,
// ...
],
'command_pipelines' => [
TransactionPipeline::class,
// ...
],
'query_pipelines' => [
QueryCachePipeline::class,
// ...
],
use App\Mediator\Pipelines\EnsureTenantContext;
use DifferentUniverse\CqbusMediator\Attributes\Handlers\CommandHandler;
use DifferentUniverse\CqbusMediator\Attributes\Pipelines\Pipeline;
#[CommandHandler]
#[Pipeline(EnsureTenantContext::class)]
final class CreateOrderHandler
{
public function handle(CreateOrder $command): Order
{
// ...
}
}
#[CommandHandler]
#[Pipeline(EnsureTenantContext::class)]
#[Pipeline(ValidateInventory::class)]
final class CreateOrderHandler
{
public function handle(CreateOrder $command): Order
{
// ...
}
}
#[CommandHandler]
#[Pipeline(EnsureTenantContext::class, ValidateInventory::class)]
final class CreateOrderHandler
{
// ...
}
final class LoggingPipeline
{
public function handle(mixed $payload, Closure $next, ?string $channel = null): mixed
{
logger()->channel($channel)->debug('Mediator dispatch', [
'payload' => $payload,
]);
return $next($payload);
}
}
#[CommandHandler]
#[Pipeline(LoggingPipeline::class . ':slack')]
final class CreateOrderHandler
{
// ...
}
use DifferentUniverse\CqbusMediator\Attributes\Pipelines\WithoutGlobalPipeline;
#[CommandHandler]
#[WithoutGlobalPipeline(LoggingPipeline::class)]
final class CreateOrderHandler
{
// ...
}
#[CommandHandler]
#[WithoutGlobalPipeline(LoggingPipeline::class)]
#[WithoutGlobalPipeline(MetricsPipeline::class)]
final class CreateOrderHandler
{
// ...
}
#[CommandHandler]
#[WithoutGlobalPipeline(LoggingPipeline::class, MetricsPipeline::class)]
final class CreateOrderHandler
{
// ...
}
#[WithoutGlobalPipeline(LoggingPipeline::class)]
final class CreateOrderHandler
{
}
#[WithoutGlobalPipeline(LoggingPipeline::class . ':slack')]
final class CreateOrderHandler
{
}
use DifferentUniverse\CqbusMediator\Attributes\Pipelines\WithoutAllGlobalPipelines;
#[QueryHandler]
#[WithoutAllGlobalPipelines]
#[Pipeline(LocalOnlyPipeline::class)]
final class LookupOrderHandler
{
// ...
}
// config/octane.php
use DifferentUniverse\CqbusMediator\Contracts\Mediator;
use Laravel\Octane\Facades\Octane;
'warm' => [
...Octane::defaultServicesToWarm(),
Mediator::class,
],
use DifferentUniverse\CqbusMediator\Contracts\Mediator;
final class CreateOrderController
{
public function __construct(
private readonly Mediator $mediator,
) {
}
public function __invoke(): JsonResponse
{
$order = $this->mediator->send(new CreateOrder(/* ... */));
return response()->json($order, 201);
}
}
use DifferentUniverse\CqbusMediator\Facades\Mediator;
$result = Mediator::send(new LookupOrder('ORD-100'));
public function send(object $message): mixed;
public function sendToChannel(string $inputChannel, mixed $payload = null): mixed;
use App\Mediator\Orders\CreateOrder;
use DifferentUniverse\CqbusMediator\Contracts\Mediator;
public function test_it_creates_an_order_through_the_mediator(): void
{
$result = $this->app->make(Mediator::class)->send(new CreateOrder(
userId: 1,
items: [['sku' => 'book', 'quantity' => 1]],
));
self::assertInstanceOf(Order::class, $result);
}
public function test_it_creates_an_order(): void
{
$repository = Mockery::mock(OrderRepository::class);
$repository->shouldReceive('create')->once()->andReturn(new OrderView('ORD-100'));
$handler = new CreateOrderHandler($repository);
$result = $handler->handle(new CreateOrder(1, []));
self::assertSame('ORD-100', $result->number);
}
$this->app->instance(PaymentGateway::class, new FakePaymentGateway());
$result = $this->app->make(Mediator::class)->send(new CapturePayment('PAY-100'));
use DifferentUniverse\CqbusMediator\Support\MediatorConfig;
protected function tearDown(): void
{
MediatorConfig::flushCachedPipelines();
parent::tearDown();
}
public function test_it_runs_command_pipelines(): void
{
config()->set('mediator.discovery_paths', [app_path('Mediator')]);
config()->set('mediator.command_pipelines', [TransactionPipeline::class]);
$result = $this->app->make(Mediator::class)->send(new CreateOrder(1, []));
self::assertNotNull($result);
}