arnedesmedt/api-platform-event-engine-bundle
Install the Bundle
composer require arnedesmedt/api-platform-event-engine-bundle
Ensure arnedesmedt/event-engine-symfony-bundle is also installed (dependency).
Configure the Bundle
Add to config/bundles.php:
return [
// ...
Arnedesmedt\ApiPlatformEventEngineBundle\ApiPlatformEventEngineBundle::class => ['all' => true],
];
Enable Event Engine
Configure arnedesmedt/event-engine-symfony-bundle in config/packages/arnedesmedt_event_engine.yaml:
arnedesmedt_event_engine:
event_store: 'doctrine' # or 'in-memory'
command_bus: 'messenger'
Define an Aggregate
Create a class implementing AggregateRoot and ChangeApiResource:
use Arnedesmedt\EventEngine\Domain\AggregateRoot;
use Arnedesmedt\EventEngine\Domain\ChangeApiResource;
class Order implements AggregateRoot, ChangeApiResource
{
use ChangeApiResourceByNamespace; // Optional trait for API resource mapping
// State and methods...
}
Map API Platform to Commands
Configure routing in config/packages/api_platform.yaml:
api_platform:
formats:
jsonld:
mime_types: ['application/ld+json']
mapping:
paths: ['%kernel.project_dir%/config/api_resources']
patch_formats:
json: ['application/merge-patch+json']
swagger:
versions: [3]
First Use Case: Create an Event-Driven API Endpoint Define a command and map it to an API Platform operation:
# config/api_resources/Order.yaml
resources:
Order:
operations:
create:
method: 'POST'
mapping:
path: '/orders'
controller: 'Arnedesmedt\ApiPlatformEventEngineBundle\Controller\CommandController'
method: 'createOrder'
input: 'App\Command\CreateOrderCommand'
Command Design
Implement JsonSchemaAwareRecord and AggregateCommand for commands:
use Arnedesmedt\EventEngine\Domain\AggregateCommand;
use Arnedesmedt\EventEngine\Domain\JsonSchemaAwareRecord;
class CreateOrderCommand implements AggregateCommand, JsonSchemaAwareRecord
{
use CommandNameAsAggregateMethod; // Auto-maps command to aggregate method
use JsonSchemaAwareRecordLogic; // Auto-generates JSON schema
public function __construct(
public string $customerId,
public array $items,
) {}
}
Aggregate Logic Define aggregate methods to handle commands:
class Order implements AggregateRoot
{
public function createOrder(CreateOrderCommand $command): void
{
$this->recordThat(new OrderCreated(
customerId: $command->customerId,
items: $command->items,
));
}
}
API Platform Integration
Use the CommandController to dispatch commands:
use Arnedesmedt\ApiPlatformEventEngineBundle\Controller\CommandController;
class OrderController extends CommandController
{
public function createOrder(CreateOrderCommand $command): void
{
$this->dispatch($command);
}
}
Query Handling
For read operations, use JsonSchemaAwareRecord queries:
class GetOrderQuery implements JsonSchemaAwareRecord
{
use JsonSchemaAwareRecordLogic;
public function __construct(public string $orderId) {}
}
Map to API Platform:
resources:
Order:
operations:
get:
method: 'GET'
mapping:
path: '/orders/{id}'
controller: 'Arnedesmedt\ApiPlatformEventEngineBundle\Controller\QueryController'
method: 'getOrder'
input: 'App\Query\GetOrderQuery'
EventEngine's event store to replay events for aggregates.JsonSchemaAwareRecord for automatic request validation via OpenAPI.Zenstruck\MessengerTest to mock the command bus in tests:
$this->bus->expects()
->dispatch(new CreateOrderCommand(...))
->thenReturn(new Order());
Aggregate Root Misconfiguration
AggregateRoot or ChangeApiResource.AggregateRoot and implement ChangeApiResource (or use the trait).Command-Operation Mismatch
CommandNameAsAggregateMethod trait to auto-map or manually configure in api_resources.yaml:
operations:
create:
method: 'POST'
mapping:
controller: 'App\Controller\OrderController::createOrder'
method: 'createOrder' # Must match aggregate method
Event Engine Dependency Injection
EventEngine services in Symfony's container.arnedesmedt/event-engine-symfony-bundle is properly configured and loaded.JSON Schema Conflicts
class CreateOrderCommand implements JsonSchemaAwareRecord
{
public function jsonSchema(): array
{
return [
'type' => 'object',
'properties' => [
'customerId' => ['type' => 'string'],
'items' => ['type' => 'array'],
],
];
}
}
Check Event Dispatching
Enable debug mode for EventEngine:
arnedesmedt_event_engine:
debug: true
Logs will show dispatched commands/events.
Validate API Platform Mappings
Use bin/console debug:api to verify operation routes and controllers:
php bin/console debug:api
Inspect Aggregate State
Override AggregateRoot::getState() to log state changes:
public function getState(): array
{
$state = parent::getState();
error_log('Aggregate state: ' . print_r($state, true));
return $state;
}
Custom Command Bus
Override the default Messenger bus by configuring arnedesmedt_event_engine:
arnedesmedt_event_engine:
command_bus: 'custom_bus' # Service ID
Event Handlers
Extend EventEngine with custom handlers:
use Arnedesmedt\EventEngine\Domain\EventHandler;
class OrderCreatedHandler implements EventHandler
{
public function handle(OrderCreated $event): void
{
// Side effects (e.g., send email)
}
}
Register in config/packages/arnedesmedt_event_engine.yaml:
arnedesmedt_event_engine:
event_handlers:
App\Event\OrderCreated: ['App\Handler\OrderCreatedHandler']
Dynamic API Resource Mapping
Use ApiPlatformEventEngineBundle's ResourceMapper to dynamically map operations:
use Arnedesmedt\ApiPlatformEventEngineBundle\Mapper\ResourceMapper;
$mapper = new ResourceMapper();
$operations = $mapper->mapResource(Order::class);
How can I help you explore Laravel packages today?