laravel-events maintained by jooservices
Laravel Events
Lightweight domain-event and model-change persistence for Laravel with MongoDB storage. Store domain event payloads by aggregate and/or model change audit trails (prev/changed/diff) via Laravel's native event dispatcher.
[!WARNING]
v4.0.0includes breaking API changes from1.x(backed enums,finalconcretes, removed bag DTOs, new indexes). SeeUPGRADE-4.0.mdand the changelog before upgrading.
This is not a full event store: there is no stream version, optimistic concurrency, replay command, or outbox. Ordering is created_at (and optional occurred_at); treat “event sourcing” here as aggregate-oriented append + query helpers.
Package name: jooservices/laravel-events
- Laravel 12/13 · PHP 8.5+ · Current release:
4.0.0 - MongoDB via mongodb/laravel-mongodb
- DTO / exceptions via jooservices/dto and jooservices/exceptions
Introduction
This package adds two persistence features on top of Laravel's event system:
- Event Sourcing (lightweight) — Events implementing
EventSourcingInterfaceare stored in astored_eventsMongoDB collection (payload, aggregate id, envelope, metadata, user, time). Use for aggregate history and audit by aggregate — not replay/outbox. - Event Log — Events implementing
LoggableModelInterfaceare stored in anevent_logscollection with previous/changed state and a per-field diff. Use for audit trails and compliance.
You dispatch events as usual; package subscribers persist them to MongoDB synchronously when the event is dispatched (unless you use Laravel after-commit / queue features yourself — see Operations). No custom bus required.
Scope
Use this package when you need a reusable Laravel-standard base library for persisting event records or audit logs.
This package does not:
- replace Laravel's event dispatcher
- provide a projection/read-model framework
- provide a business analytics or reporting layer
- provide dashboards, projections, or analytics/reporting workflows
- provide AI agents, AI data fetching, or an AI runtime
When to Use
| Need | Use |
|---|---|
| Persist domain events by aggregate for historical inspection or replay-aware workflows | Event Sourcing |
| Persist model/entity changes with previous/current values and field diff | Event Log |
| Need both domain history and field-level audit | Use both, with separate focused events |
| Need dashboards, projections, analytics, or AI retrieval | Build that in the application layer |
Quick Start
Install
composer require jooservices/laravel-events
Publish config (optional)
php artisan vendor:publish --tag=laravel-events-config
Environment
MONGODB_URI=mongodb://127.0.0.1:27017
MONGODB_DATABASE=your_db
EVENTS_EVENTSOURCING_ENABLED=true
EVENTS_EVENT_LOG_ENABLED=true
Ensure a mongodb connection exists in config/database.php (see mongodb/laravel-mongodb).
Indexes
php artisan events:install-indexes
Basic Usage
Event Sourcing
Implement EventSourcingInterface and dispatch:
use JOOservices\LaravelEvents\EventSourcing\Contracts\EventSourcingInterface;
class OrderCreated implements EventSourcingInterface
{
public function __construct(public string $orderId, public array $items) {}
public function payload(): array
{
return ['order_id' => $this->orderId, 'items' => $this->items];
}
public function aggregateId(): ?string
{
return $this->orderId;
}
}
event(new OrderCreated('ORD-001', [['sku' => 'X', 'qty' => 2]]));
Events are stored in the stored_events collection. Optional: occurredAt(): ?\Carbon\CarbonInterface, metadata(): array. Use the HasEventSourcingDefaults trait to implement only payload() and aggregateId().
Recommended metadata keys include request_id, correlation_id, causation_id, source, channel, reason_code, schema_version, event_version, event_category, and optional tenant_id. The EventMetadata helper exposes constants and small factory methods for those conventions.
Use event_category for a lightweight event type convention when you need broad categories such as domain, integration, audit, or system without introducing a full event taxonomy framework.
Event Log (Audit)
Implement LoggableModelInterface (and optionally HasLogAction) and dispatch with prev/changed state. Use the DefaultsToUpdatedAction trait when the action is always updated:
use JOOservices\LaravelEvents\EventLog\Concerns\DefaultsToUpdatedAction;
use JOOservices\LaravelEvents\EventLog\Contracts\LoggableModelInterface;
use JOOservices\LaravelEvents\EventLog\Contracts\HasLogAction;
class OrderUpdated implements LoggableModelInterface, HasLogAction
{
use DefaultsToUpdatedAction;
public function __construct(public Order $model, public array $prev) {}
public function getLoggableType(): string { return $this->model->getMorphClass(); }
public function getLoggableId(): string { return (string) $this->model->getKey(); }
public function getPrev(): array { return $this->prev; }
public function getChanged(): array { return $this->model->getAttributes(); }
}
Changes are stored in event_logs with a computed diff. Query by entity_type + entity_id.
Recommended action names are available from JOOservices\LaravelEvents\EventLog\EventLogAction: created, updated, deleted, restored, status_changed, corrected, synchronized, and imported.
Querying
use JOOservices\LaravelEvents\Query\EventLogQueryService;
use JOOservices\LaravelEvents\Query\StoredEventQueryService;
$events = app(StoredEventQueryService::class)->byAggregateId('ORD-001');
$domainEvents = app(StoredEventQueryService::class)->byEventCategory('domain');
$audit = app(EventLogQueryService::class)->byEntity('orders', 'ORD-001');
Query services return typed package data records (including storage id / created_at when present) and intentionally stay small.
Build dashboards, projections, and reporting in your application.
The public PHP namespace is JOOservices\LaravelEvents only (alternate casing removed in 1.5.0). See UPGRADE-4.0.md for 4.0.0 breaking changes (enums, final, indexes).
Redaction
Recursive redaction is enabled by default for common secret keys:
'redaction' => [
'enabled' => true,
'keys' => [
'password',
'password_hash',
'token',
'secret',
'client_secret',
'private_key',
'api_key',
'authorization',
],
'replacement' => '[REDACTED]',
],
This masks stored event payload/metadata and event log prev, changed,
diff, and meta. It is defensive masking, not a replacement for avoiding
secrets in dispatched events.
Retention
Optional MongoDB TTL indexes are configured with:
EVENTS_STORED_EVENTS_RETENTION_DAYS=
EVENTS_EVENT_LOGS_RETENTION_DAYS=365
Run php artisan events:install-indexes after changing retention settings.
MongoDB TTL deletion is asynchronous.
Bulk Records
use JOOservices\LaravelEvents\Data\StoredEventData;
use JOOservices\LaravelEvents\EventService;
app(EventService::class)->recordManyStoredEvents([
new StoredEventData('OrderImported', ['order_id' => 'ORD-001'], 'ORD-001'),
]);
Bulk APIs normalize and redact each record before MongoDB batch insert.
Documentation
Full documentation is in the ./docs folder:
| Document | Description |
|---|---|
| docs/README.md | Documentation index |
| Architecture | Design, data flow, diagrams |
| Code structure | Package layout and namespaces |
| Installation | Requirements and setup |
| Configuration | Config and context provider |
| Decision Guide | Event Sourcing vs Event Log |
| Event Sourcing | Stored events and aggregates |
| Event Log | Audit trail and diff |
| Metadata | Metadata keys, versioning, corrections |
| Operations | Indexes, query patterns, retention, production safety |
| AI Integration | Optional app-layer AI export examples |
| Development | Composer commands, CI, release, and contributor workflow |
| Samples | Complete code examples |
| API Reference | EventService, interfaces, commands |
Testing & Linting
composer test
composer test:coverage
composer lint # Pint, PHPCS, PHPStan, PHPMD, PHP-CS-Fixer
composer lint:fix # Pint fix + PHP-CS-Fixer fix
composer check # lint + test
composer ci # lint + test:coverage
Git Hooks
Composer installs Git hooks automatically on dependency install and update:
composer install
composer update
The hooks are managed by CaptainHook and enforce:
commit-msg: Conventional Commits, for examplefix: Correct event metadata mergepre-commit: PHP syntax linting, staged secret scanning with gitleaks, Pint, PHPCS, PHPStan, PHPMD, and PHP-CS-Fixerpre-push: gitleaks history scan when available, thencomposer test
If hooks need to be reinstalled manually:
vendor/bin/captainhook install --force --skip-existing
Install gitleaks locally to pass the pre-commit secret scan:
brew install gitleaks
AI Contributor Support
AI contributor guidance is intentionally documentation-only:
The package does not include AI runtime code, AI data fetching, authorization, redaction, or tool execution.
DTO-Style Records
The package uses small typed data records for normalized stored event and audit
log data. It follows jooservices/dto as a maturity baseline for repository
quality and docs structure, without depending on DTO domain internals.
GitHub Actions
Configured workflows:
CI: Composer metadata validation, Composer audit, Pint, PHPCS, PHPStan, PHPMD, PHP-CS-Fixer, PHPUnit coverage with a MongoDB service, a 95% minimum statement coverage gate, guarded Codecov upload, guarded SonarQube Cloud analysis, and non-blocking dependency review for pull requestsRelease: validate version tags, create GitHub releases, and trigger Packagist updates when Packagist secrets are configuredPR Labeler: apply labels based on changed filesSemantic PR Title: enforce Conventional Commit-style PR titlesOpenSSF Scorecard: publish security posture results as SARIFSecret Scanning: run Gitleaks on pushes, pull requests, and manual dispatches
Coverage is archived as a workflow artifact. Codecov and SonarQube Cloud run when repository secrets are configured; README badges mirror dto / client (CI on develop, version → CHANGELOG, Sonar, Scorecard).
License
This project is licensed under the MIT License.