Internal System Events
Overview
DomainFlow Core emits a variety of system events throughout its lifecycle. These internally generated events allow you to monitor, log, and react to key operations without modifying core functionality. By tapping into these events, you can extend or customize behaviors for tasks like bootstrapping, caching, middleware execution, and application termination.
Boot Lifecycle Events
- booting.init: Fired when a boot process actually starts (i.e. the application was not already booted). Use this event to run early initialization logic.
- booting.repeat_call_ignored: Fired instead of booting.init when
boot()is called again on an application that is already booted. No boot work runs on this call. This keeps the event trail unambiguous: an auditor scanning it can rely on booting.init/booting.complete pairs meaning a real boot happened, and booting.repeat_call_ignored meaning it did not. - booting.error: Triggered if an error occurs during the boot callbacks, provider registration, or provider boot, allowing you to catch and log startup issues.
- booting.complete: Emitted once eager providers and booted callbacks have completed successfully and the application has set its boot flag. Deferred providers may still be unloaded until requested or explicitly loaded.
Path and Configuration Events
- path.base.set: Fired when the application successfully sets the base path.
- path.base.error: Emitted if an invalid base path is provided.
- path.config.set: Triggered when the configuration path is correctly set.
- path.config.error: Fired if there is an error setting the configuration path.
- path.environment.set: Emitted when the current environment (such as production or development) is updated.
Cache-Related Events
The declarative container cache (e.g. FileContainerCache, InMemoryContainerCache) that backs the base Container's definitions cache does not fire any cache.* system events on get()/set()/has()/delete(). If your application still listens for events like cache.saved or cache.loaded from an older DomainFlow Core version, those listeners are now dead code — remove them, or switch to inspecting cache freshness directly (see below). Cache configuration itself (e.g. setExternalCache()) is part of the base Container and documented separately in the Container docs.
Core doesn't fire cache lifecycle events itself, but two related capabilities are worth knowing about when working with caching:
- Resource-tracked cache freshness:
FileContainerCache::trackResource(string $key, string $resourceFile)opts a cache key into freshness tracking, comparable to Symfony'sConfigCache::isFresh().loadServiceDefinitions()(see Service Definitions) calls this automatically for the loaded file, so an edited.yaml/.json/.phpservice-definition file self-invalidates a persisted cache entry instead of silently serving stale bindings — even across process boundaries. - Bounded system-event retention:
SystemEventStore::setMaxRetainedEvents(int $max)caps how many of these system-event firings are retained in memory — see Bounded System-Event Retention below. This is unrelated to the container's definitions cache; it's about the event store you're reading this page to understand.
Middleware Pipeline Events
- middleware.pipeline.start: Fired at the beginning of middleware pipeline execution.
- middleware.pipeline.end: Emitted when the middleware pipeline has completed processing.
- middleware.error: Triggered if middleware or the final handler fails inside the protected pipeline body. A failure in the
middleware.pipeline.startlistener occurs before that body.
Termination Events
- termination.init: Fired when the termination process begins.
- termination.complete: Emitted only after every termination callback has completed successfully.
- termination.error: Triggered on the first termination callback failure; later callbacks are skipped and
termination.completeis not emitted.
Service Provider Events
- service_provider.registered: Fired when a service provider is successfully registered (i.e. its
register()has run). - service_provider.unregistered: Emitted when a provider is removed from the application via
unregisterProvider(). - service_provider.deferred.loaded: Triggered when a deferred provider is registered and booted on-demand — fired before the identifier is removed from the deferred map.
- service_provider.deferred.removed: Fired immediately after service_provider.deferred.loaded, once the resolved identifier has been removed from the deferred map.
Service Definition Events
Fired by loadServiceDefinitions() while loading a service-definition file.
- service_definition.file.parsed: Fired once a definitions file has been read and parsed, before any individual definition is processed.
- service_definition.bound: Fired for each service definition after it has been successfully bound into the container.
- service_definition.error: Fired when an individual definition is malformed (e.g. not an array, an invalid
concrete/factory, or an invalid tag), or when binding it fails. File-reading/parsing errors and invalid top-level document shapes occur before the per-definition error wrapper and may be thrown without this event.
Event Manager Specific Events
- event_manager.dispatcher.set: Fired when a new event dispatcher is set (including the initial one set in the
Applicationconstructor). - event_manager.dispatch.error: Emitted if an error occurs during the dispatching of an event.
Bounded System-Event Retention
By default, SystemEventStore (the default SystemEventStoreInterface implementation) retains every successfully dispatched event in memory for the lifetime of the Application instance, unless clear() is called. A failed original event is not stored, and the internally dispatched event_manager.dispatch.error event is not added to the store by that error path. That's fine for a short-lived request-scoped process, but it's a real risk for a long-running worker, daemon, or queue-consumer process.
Signature
public function setMaxRetainedEvents(?int $maxRetainedEvents): void
Purpose
Caps total retained event firings — across all event names combined — at $maxRetainedEvents. Once a new firing would exceed the cap, the globally oldest firing (regardless of which event name it belongs to) is evicted — dropped, not drained anywhere — before the new one is appended. An event name with no firings left after an eviction is removed from getEvents() entirely rather than lingering as an empty array. getSortedEvents() reflects the same bounded set.
| Parameter | Type | Required? | Description |
|---|---|---|---|
$maxRetainedEvents | ?int | Yes | Positive integer cap, or null to restore unbounded retention. |
Usage Example
$store = new SystemEventStore();
$store->setMaxRetainedEvents(500); // never hold more than 500 firings total
$app = new Application(systemEventStore: $store);
setMaxRetainedEvents(null)restores unbounded retention without discarding what is currently held.- Calling
clear()also resets the internal eviction bookkeeping, but the configured cap itself survives aclear()call. - Not calling
setMaxRetainedEvents()at all leaves retention exactly as it always was — fully unbounded, zero added overhead. - Passing anything less than
1(and notnull) throwsInvalidArgumentException.
Consuming System Events at Runtime
You can subscribe to these system events to execute custom logic during your application's lifecycle. Below is an example that demonstrates how to attach event listeners for key system events and perform actions such as logging, debugging, or triggering alerts.
Example Usage
// Assume $app is your DomainFlow Core application instance.
// Listen for the boot process start event.
$app->on('booting.init', function ($app) {
echo "Boot process started. Initializing components...\n";
});
// Listen for the middleware pipeline start event.
$app->on('middleware.pipeline.start', function ($payload) {
error_log("Middleware pipeline started with payload: " . json_encode($payload));
});
// Listen for the termination complete event.
$app->on('termination.complete', function ($app) {
echo "Application terminated successfully.\n";
});
// Simulate application lifecycle events for demonstration.
// Start the boot process (this will trigger the 'booting.init' event).
$app->boot();
// Execute the middleware pipeline with an initial payload and a final callback.
$result = $app->pipeline(['data' => 'example'], function ($payload) {
// Final processing logic.
return "Processed payload: " . json_encode($payload);
});
echo $result;
// Terminate the application (this will trigger the 'termination.complete' event).
$app->terminate();
In this example, custom listeners are attached to various system events. As the application boots, processes middleware, and eventually terminates, these listeners output messages and log details, helping you monitor the inner workings of your application in real time.
Extensibility
- Custom Logging: By subscribing to events like booting.error or middleware.error, you can set up custom logging or alerting mechanisms.
- Performance Monitoring: Use events such as middleware.pipeline.start and middleware.pipeline.end to measure processing times and optimize performance.
- Dynamic Behavior: Hook into termination or configuration events to trigger cleanup tasks or reconfigure services on the fly.
This system of internally generated events offers a powerful way to observe and modify the application's behavior without altering its core code. Enjoy leveraging these events to build a more responsive and maintainable application!