event-engine/php-engine
CQRS/Event Sourcing framework for PHP to rapidly build event-sourced applications and evolve toward richer domain models. Customize architecture and programming style via “Flavours,” with a full tutorial and separate documentation repo.
Installation
composer require event-engine/php-engine
Add the service provider to config/app.php:
'providers' => [
// ...
EventEngine\Engine\EngineServiceProvider::class,
],
Basic Event Definition
Define an event class (e.g., UserRegistered):
namespace App\Events;
use EventEngine\Engine\Event;
class UserRegistered extends Event
{
public function __construct(public string $userId) {}
}
Registering Listeners Bind listeners in a service provider or boot method:
$engine = app('event-engine');
$engine->listen(UserRegistered::class, function ($event) {
// Handle event logic
});
Triggering Events Dispatch events via the engine:
$engine = app('event-engine');
$engine->dispatch(new UserRegistered('user-123'));
$engine->listen(UserRegistered::class, function ($event) {
\Log::info("User registered: {$event->userId}");
});
Decoupling Logic Use events to separate business logic from execution (e.g., notifications, analytics, audits).
// Instead of:
$userService->register($user);
$notificationService->sendWelcome($user);
// Do:
$engine->dispatch(new UserRegistered($user->id));
Middleware for Events Apply middleware to events (e.g., rate limiting, auth checks):
$engine->listen(UserRegistered::class)
->middleware(CheckUserRole::class)
->then(function ($event) {
// Core logic
});
Async Processing Offload heavy tasks to queues:
$engine->listen(UserRegistered::class)
->queueOn('high-priority')
->then(function ($event) {
// Queue job here
});
Laravel Integration
Extend Laravel’s event system by wrapping EventEngine in a facade:
// app/Providers/AppServiceProvider.php
public function boot()
{
app()->singleton('event-engine', function () {
return app('event-engine');
});
}
Use in controllers:
use Illuminate\Support\Facades\EventEngine;
EventEngine::dispatch(new UserRegistered($userId));
Dynamic Listeners Load listeners from config or database:
$engine->loadListenersFromConfig('events.listeners');
Event Retries Implement retry logic for failed listeners:
$engine->listen(UserRegistered::class)
->retry(3, 100) // Retry 3 times with 100ms delay
->then(...);
Circular Dependencies
Avoid circular event triggers (e.g., EventA triggers EventB, which triggers EventA again).
Solution: Use a triggered flag or middleware to break cycles.
Memory Leaks Unbound listeners persist until the app shuts down. Explicitly unbind if needed:
$engine->forget(UserRegistered::class);
Priority Collisions Listeners with the same priority may execute unpredictably. Solution: Use explicit priorities:
$engine->listen(UserRegistered::class, fn($e) => ...)
->priority(100); // Higher priority runs first
dd($engine->listeners());
$engine->debug(true);
Custom Event Stores Override the default in-memory store for persistence:
$engine->setStore(new DatabaseEventStore());
Event Filters Filter events dynamically:
$engine->listen(UserRegistered::class)
->filter(fn($event) => $event->userId === 'admin')
->then(...);
Event Metadata Attach metadata to events for observability:
$event = new UserRegistered('user-123');
$event->setMetadata(['source' => 'api']);
$engine->setTimeout(30); // 30 seconds
database, redis) are properly configured in .env.How can I help you explore Laravel packages today?