VOOZH about

URL: https://deepwiki.com/hypervel/event/5.3-deferred-events

⇱ Deferred Events | hypervel/event | DeepWiki


Loading...
Menu

Deferred Events

This page covers the event deferral mechanisms in the EventDispatcher, which allow events to be temporarily held and dispatched later. Two distinct deferral mechanisms are provided: the defer() method for batching events within a callback execution, and the push()/flush() methods for manual event queuing. These mechanisms enable control over event timing, reduce unnecessary dispatches, and allow batching of related events.

For transaction-aware event deferral based on database commits, see Dispatching Events After Commit and Handling Events After Commit.


Overview of Deferral Mechanisms

The event system provides two independent deferral approaches:

MechanismPurposeScopeAPI Methods
Programmatic DeferralBatch events within a callback executionTemporary, callback-scopeddefer(), shouldDeferEvent()
Push/Flush PatternManually queue and trigger eventsPersistent until flushedpush(), flush(), forgetPushed()

Both mechanisms use the Hyperf Context system for state management, ensuring proper isolation in coroutine environments.

Sources: src/EventDispatcher.php573-617


Programmatic Deferral with defer()

Basic Concept

The defer() method executes a callback while temporarily holding all dispatched events in memory. Events are batched and then dispatched in order after the callback completes successfully.


During the callback execution, any events dispatched are stored in context memory rather than being immediately processed. Once the callback returns, all deferred events are dispatched in the order they were originally triggered.

Mechanism Diagram


Sources: src/EventDispatcher.php573-598 src/EventDispatcher.php603-617

Context State Management

The defer() method manages three context variables:

Context KeyTypePurpose
__event.deferringboolFlag indicating if currently inside a defer block
__event.deferred_eventsarrayBuffer storing [event, payload, halt] tuples
__event.events_to_defer?arrayOptional whitelist of event classes to defer

The implementation preserves previous state to support nested defer() calls:

src/EventDispatcher.php575-577 - Previous state is saved before entering defer mode src/EventDispatcher.php579-581 - New defer state is activated src/EventDispatcher.php594-596 - Previous state is restored in finally block

Selective Event Deferral

By default, all events are deferred within a defer() callback. An optional $events parameter enables selective deferral:


The shouldDeferEvent() method implements the filtering logic:

src/EventDispatcher.php603-617 - Returns false if not deferring, true if no whitelist exists, or checks if event class is in whitelist

Sources: src/EventDispatcher.php573-598 src/EventDispatcher.php603-617

Nested Defer Blocks

The defer mechanism supports nesting through state preservation. Each nested defer() call:

  1. Saves the parent's defer state
  2. Establishes its own defer context
  3. Processes its deferred events
  4. Restores the parent's state

The finally block ensures state restoration even if exceptions occur:

src/EventDispatcher.php593-597 - State restoration in finally block guarantees cleanup

Sources: src/EventDispatcher.php573-598


Push/Flush Pattern

Concept and Registration

The push/flush pattern provides manual control over event timing by registering events to be fired later:


Unlike defer(), the push/flush mechanism is persistent across function boundaries and must be explicitly flushed.

Implementation Strategy

The push mechanism works by registering a special listener for a suffixed event name:

src/EventDispatcher.php306-309 - Creates listener for {event}_pushed that will dispatch the original event

When push() is called with event name "order.created", it registers a listener for "order.created_pushed" that will dispatch "order.created" when triggered.

Flush Mechanism

The flush() method triggers all pushed events for a specific event name:

src/EventDispatcher.php316-318 - Dispatches the {event}_pushed event, triggering all registered listeners


Sources: src/EventDispatcher.php305-318

Cleanup with forgetPushed()

The forgetPushed() method removes all pushed event listeners without firing them:

src/EventDispatcher.php323-330 - Iterates through all listeners and forgets those ending with _pushed

This is useful when pushed events should be discarded due to error conditions or transaction rollbacks.

Sources: src/EventDispatcher.php323-330


Integration with Dispatch Flow

Dispatch Pipeline

The deferred event check occurs at the very beginning of the dispatch pipeline:


This ordering ensures:

  1. Programmatic deferral via defer() takes precedence
  2. Transaction-based deferral is checked next
  3. Normal dispatch proceeds if neither applies

src/EventDispatcher.php64-73 - Deferred event check and storage src/EventDispatcher.php78-85 - Transaction-based deferral check

Sources: src/EventDispatcher.php62-88

Context Override Pattern

Event deferral uses Context::override() to safely append events to the array:

src/EventDispatcher.php65-70 - Uses override callback to safely modify array in context

This pattern prevents race conditions in coroutine environments by ensuring atomic updates to the deferred events array.

Sources: src/EventDispatcher.php64-73


Use Cases and Patterns

Batch Processing Optimization

Defer events during bulk operations to reduce overhead:


This batches all UserCreated events and dispatches them once after the loop completes.

Consistency During Complex Operations

Ensure all events fire together after a multi-step operation succeeds:


If any step throws an exception, no events are dispatched.

Push/Flush for Conditional Events

Use push/flush when event timing depends on external conditions:


Selective Deferral with Event Filtering

Defer only specific events while allowing others to execute immediately:


Sources: src/EventDispatcher.php573-617


Comparison with Other Deferral Mechanisms

Featuredefer()push()/flush()ShouldDispatchAfterCommit
ScopeCallback-scopedPersistent until flushedTransaction-scoped
Automatic DispatchYes, after callbackNo, requires explicit flushYes, after DB commit
Event FilteringSupports whitelistNo filteringAutomatic based on interface
Nesting SupportYes, with state isolationN/AYes, via transaction nesting
Cleanup on ErrorAutomatic (events not dispatched)Manual (requires forgetPushed)Automatic (transaction rollback)
Use CaseBatching, consistencyConditional firingData integrity
Context Storage__event.deferred_eventsListener registry with _pushed suffixTransactionManager callbacks

All three mechanisms can be combined. For example, a deferred event within a defer() block could also implement ShouldDispatchAfterCommit.

Sources: src/EventDispatcher.php64-85 src/EventDispatcher.php305-318 src/EventDispatcher.php573-617


Implementation Details

Context Variables Reference

VariableTypeManaged ByPurpose
__event.deferringbooldefer()Flag indicating active defer block
__event.deferred_eventsarraydefer(), shouldDeferEvent()Buffer of events to dispatch
__event.events_to_defer?arraydefer()Optional whitelist of event classes

State Transition Diagram


Sources: src/EventDispatcher.php573-617

Exception Safety

The defer() method uses a try-finally block to ensure state restoration even when exceptions occur:

src/EventDispatcher.php583-597 - Try-finally ensures context cleanup

If an exception is thrown during the callback:

  1. The finally block restores the previous context state
  2. Deferred events are not dispatched (they remain buffered)
  3. The exception propagates to the caller

This guarantees that partial operations don't trigger events, maintaining consistency.

Sources: src/EventDispatcher.php573-598


Performance Considerations

Memory Usage

Deferred events are held in memory until dispatched. Each deferred event stores:

  • The event object (or string name)
  • The payload
  • The halt flag

For bulk operations, this can accumulate significant memory. Consider:

  • Using defer() for moderate batch sizes (hundreds to thousands)
  • Breaking very large batches into chunks with multiple defer() calls
  • Monitoring memory usage during bulk imports or processing

Execution Overhead

The shouldDeferEvent() method is called for every event dispatch within a defer block:

src/EventDispatcher.php603-617 - Per-event check with context lookups

The overhead is minimal for typical use cases but becomes measurable in high-frequency event scenarios.

Context Isolation

In Hyperf's coroutine environment, context variables provide isolation between concurrent requests. The defer mechanism is fully coroutine-safe because each coroutine maintains its own context storage.

Sources: src/EventDispatcher.php64-73 src/EventDispatcher.php603-617