VOOZH about

URL: https://deepwiki.com/hypervel/event/4-transaction-aware-events

⇱ Transaction-Aware Events | hypervel/event | DeepWiki


Loading...
Menu

Transaction-Aware Events

This document explains the database transaction integration layer in the hypervel/event package. It covers how events and listeners can be deferred until database transactions successfully commit, the two integration points available (event-level and listener-level), and the internal mechanisms that enable this behavior.

For practical implementation guides, see Dispatching Events After Commit and Handling Events After Commit. For combining transaction awareness with asynchronous processing, see Configuring Queued Events.

Purpose and Data Consistency

Transaction-aware events solve a critical problem in event-driven applications: ensuring that event side effects only occur when database operations successfully complete. Without transaction awareness, an event might trigger actions (sending emails, updating caches, notifying external services) based on database changes that are later rolled back, leading to data inconsistency.

The event system provides two mechanisms for transaction awareness:

MechanismInterfaceScopeUse Case
Event-LevelShouldDispatchAfterCommitEntire event dispatch deferredEvent represents a domain action that must only occur after successful persistence
Listener-LevelShouldHandleEventsAfterCommitIndividual listener deferredSpecific listeners have side effects requiring committed data

Both mechanisms depend on the TransactionManager from hypervel/database. When the transaction manager is unavailable, the system gracefully falls back to immediate execution.

Sources: src/EventDispatcher.php78-85 src/Contracts/ShouldDispatchAfterCommit.php1-9 src/Contracts/ShouldHandleEventsAfterCommit.php1-9

Transaction-Aware Event Flow


Diagram: Transaction-Aware Event Execution Flow

This sequence illustrates how ShouldDispatchAfterCommit events are deferred. When dispatch() is called within a database transaction, the EventDispatcher registers a callback with the TransactionManager instead of executing listeners immediately. The callback is only invoked after the database transaction successfully commits.

Sources: src/EventDispatcher.php78-85 src/EventDispatcher.php157-174

Code Entity Mapping


Diagram: Transaction-Aware Event Code Entity Map

This diagram maps the transaction awareness feature to specific code entities in the EventDispatcher class. The two interface contracts (ShouldDispatchAfterCommit and ShouldHandleEventsAfterCommit) trigger different code paths that ultimately interact with the TransactionManager through the resolveTransactionManager() method.

Sources: src/EventDispatcher.php44-47 src/EventDispatcher.php78-85 src/EventDispatcher.php263-282 src/EventDispatcher.php287-292 src/EventDispatcher.php379-392 src/EventDispatcher.php429-440

Event-Level Transaction Awareness

Event-level transaction awareness defers the entire event dispatch process until the transaction commits. When an event implements ShouldDispatchAfterCommit, the dispatch() method checks for this interface before retrieving listeners.

Detection Logic

The detection occurs in dispatch() at lines 78-85:


The system performs two checks:

  1. The event object implements ShouldDispatchAfterCommit
  2. A TransactionManager is available via the resolver

If both conditions are met, the entire invokeListeners() call is wrapped in a callback and registered with the transaction manager. The event object is returned immediately without executing any listeners.

Execution Timing

The registered callback executes when:

  • The outermost database transaction commits successfully
  • All transaction operations complete without errors
  • The transaction manager invokes all registered callbacks

If the transaction rolls back, the callback is discarded and listeners never execute.

Sources: src/EventDispatcher.php78-85 src/EventDispatcher.php157-174 src/Contracts/ShouldDispatchAfterCommit.php1-9

Listener-Level Transaction Awareness

Listener-level transaction awareness defers individual listeners while allowing the event to dispatch normally. This provides finer-grained control when only certain listeners require committed data.

Detection Mechanisms

The system detects listener-level transaction awareness through two methods:

MethodCheck LocationCode Reference
Interface Implementationinstanceof ShouldHandleEventsAfterCommitsrc/EventDispatcher.php290
Property Flag$listener->afterCommit propertysrc/EventDispatcher.php289

The check occurs in handlerShouldBeDispatchedAfterDatabaseTransactions():


This method returns true if either:

  • The listener has an afterCommit property set to a truthy value
  • The listener implements ShouldHandleEventsAfterCommit
  • AND a TransactionManager is available

Callback Creation

When a listener should be deferred, createClassCallable() (lines 263-282) delegates to createCallbackForListenerRunningAfterCommits() instead of returning the listener method directly:


The callback wrapper (lines 429-440) captures the listener, method, and arguments, then registers a callback with the transaction manager:


Unlike event-level deferral, the event dispatches immediately, but individual transaction-aware listeners register their own callbacks.

Sources: src/EventDispatcher.php263-282 src/EventDispatcher.php287-292 src/EventDispatcher.php429-440 src/Contracts/ShouldHandleEventsAfterCommit.php1-9

Transaction Manager Integration


Diagram: TransactionManager Integration Architecture

The EventDispatcher integrates with the TransactionManager through a resolver pattern. The transactionManagerResolver property stores a callable that returns the transaction manager instance when invoked.

Resolver Pattern

The resolver is set during factory initialization via setTransactionManagerResolver() (lines 387-392):


When transaction awareness is needed, resolveTransactionManager() (lines 379-382) invokes the resolver:


This lazy resolution pattern has several benefits:

  • The transaction manager is only retrieved when needed
  • Circular dependency issues are avoided
  • The system can gracefully handle missing transaction managers

Fallback Behavior

When resolveTransactionManager() returns null (transaction manager not available), the system falls back to immediate execution. This occurs at two decision points:

  1. Event-level: Line 79 checks ! is_null($transactions = $this->resolveTransactionManager()). If null, execution continues to invokeListeners() without deferral.

  2. Listener-level: Line 291 requires $this->resolveTransactionManager() to be truthy. If null, handlerShouldBeDispatchedAfterDatabaseTransactions() returns false, and the listener executes immediately.

This graceful degradation allows the event system to function in environments without database transaction support.

Sources: src/EventDispatcher.php44-47 src/EventDispatcher.php78-85 src/EventDispatcher.php287-292 src/EventDispatcher.php379-392

Combining Transaction Awareness and Queue System

Transaction-aware behavior can be combined with asynchronous queue processing. When a queued listener also requires transaction awareness, the system propagates the afterCommit property to the queue job.

Propagation Logic

During queue job creation, propagateListenerOptions() (lines 497-522) transfers listener configuration to the CallQueuedListener job. Lines 502-506 handle the afterCommit property:


The system checks for:

  1. ShouldQueueAfterCommit interface: Explicitly sets afterCommit = true on the job
  2. afterCommit property: Copies the property value from listener to job

This propagation ensures that when the queue worker processes the job, it respects the transaction requirements.

Execution Flow with Both Features


Diagram: Combined Transaction and Queue Processing

When a listener implements both ShouldQueue and has transaction awareness (afterCommit property or ShouldHandleEventsAfterCommit), the queueing operation itself is deferred until the transaction commits. This prevents jobs from being queued for operations that may be rolled back.

The execution order is:

  1. Event dispatches
  2. Listener identified as both queued and transaction-aware
  3. Instead of immediate queueing, a callback is registered with TransactionManager
  4. Transaction commits
  5. Callback executes, pushing job to queue
  6. Queue worker processes job and executes listener

Sources: src/EventDispatcher.php413-424 src/EventDispatcher.php459-478 src/EventDispatcher.php497-522

Integration Point Comparison


Diagram: Comparison of Transaction Awareness Integration Points

Decision Criteria

ScenarioRecommended ApproachRationale
All listeners need committed dataEvent-level (ShouldDispatchAfterCommit)Simpler, defers entire event as unit
Only some listeners need committed dataListener-level (ShouldHandleEventsAfterCommit)Flexible, immediate listeners can execute
Event represents completed domain actionEvent-levelSemantic clarity: event only exists after commit
Listener has external side effectsListener-levelFine-grained control per listener
Broadcasting the eventEvent-levelPrevents broadcast before data persisted

Performance Considerations

Event-level deferral has slightly lower overhead because:

  • Single callback registered with transaction manager
  • No per-listener checks during listener retrieval
  • All listeners batched in one deferred execution

Listener-level deferral provides flexibility at the cost of:

  • Per-listener transaction checks
  • Multiple callbacks registered with transaction manager
  • Mixed immediate and deferred execution

Sources: src/EventDispatcher.php78-85 src/EventDispatcher.php263-292 src/EventDispatcher.php429-440

Configuration and Factory Setup

The transaction manager resolver is typically configured during application bootstrap through the EventDispatcherFactory. The factory retrieves the transaction manager from the container and sets up the resolver:


This setup pattern:

  • Delays transaction manager retrieval until first use
  • Handles missing dependency gracefully
  • Avoids circular dependency issues during bootstrap

For factory implementation details, see Factory System and Dependency Injection.

Sources: src/EventDispatcher.php387-392