VOOZH about

URL: https://deepwiki.com/hypervel/notifications/3-core-architecture

⇱ Core Architecture | hypervel/notifications | DeepWiki


Loading...
Menu

Core Architecture

Purpose and Scope

This document describes the overall architecture of the hypervel/notifications system, focusing on how the three major components—ChannelManager, NotificationSender, and the queue integration layer—work together to deliver notifications through various channels. This page provides the high-level view of component relationships, data flow, and decision points in the notification lifecycle.

For detailed information on individual components, see:


Architectural Layers

The notification system is organized into five distinct architectural layers, each with specific responsibilities:

LayerComponentsResponsibilities
Framework IntegrationNotificationServiceProvider, ConfigProviderBootstrap views, register configuration, bind interfaces to dependency injection container
Core OrchestrationChannelManager, NotificationSender, SendQueuedNotificationsRoute notifications to channels, manage driver lifecycle, orchestrate synchronous and asynchronous delivery
Message ConstructionMailMessage, SlackMessage, BroadcastMessage, SimpleMessageBuild channel-specific message payloads with formatting, attachments, and metadata
Delivery ChannelsMailChannel, BroadcastChannel, DatabaseChannel, SlackNotificationRouterChannelInterface with external systems to actually deliver notifications
External SystemsQueue dispatcher, event dispatcher, mail factory, broadcasting serviceProvide infrastructure services (queueing, events, email, WebSockets)

This layered architecture enforces separation of concerns: message construction is decoupled from delivery, synchronous and asynchronous processing use the same delivery channels, and the orchestration layer coordinates between all other layers.

Sources: src/ChannelManager.php1-233 src/NotificationSender.php1-199 src/SendQueuedNotifications.php1-154


Primary Components Overview

ChannelManager: Central Dispatcher

The ChannelManager class serves as the primary entry point for the notification system. It acts as a factory and registry for notification channels (also called "drivers"), providing:

  • Driver resolution: Resolves channel names to concrete channel implementations through custom creators, built-in factory methods, or container lookup
  • Instance pooling: Wraps expensive drivers (e.g., Slack) in NotificationPoolProxy for connection reuse
  • Context management: Stores request-scoped configuration (default channel, locale) in Hyperf Context without global state mutation
  • Delegation: Immediately delegates actual sending logic to NotificationSender

The manager extends Hypervel\Support\Manager and implements both DispatcherContract and FactoryContract interfaces.

Sources: src/ChannelManager.php22-233

NotificationSender: Orchestration Engine

The NotificationSender class implements the core notification delivery logic. It:

  • Routes to channels: Calls notification->via() to determine which channels should receive the notification
  • Handles locale: Applies locale preferences from notifiable entities or explicit configuration
  • Manages sync vs async: Checks if notification implements ShouldQueue to decide between immediate and queued delivery
  • Dispatches events: Fires NotificationSending (cancellable) and NotificationSent events around channel delivery
  • Generates IDs: Creates UUIDs for notifications to enable tracking

Each notification send operation creates a new NotificationSender instance, ensuring clean state per request.

Sources: src/NotificationSender.php22-199

SendQueuedNotifications: Queue Job

The SendQueuedNotifications class is a queue job that wraps notifications for asynchronous processing. It:

  • Extracts configuration: Pulls queue settings (connection, queue name, tries, timeout, maxExceptions) from the notification instance
  • Serializes notifiables: Wraps notifiable entities in a Collection for serialization
  • Delegates to ChannelManager: Calls ChannelManager->sendNow() when the job is processed
  • Handles failures: Invokes notification->failed() on permanent failure
  • Supports retry logic: Consults notification->backoff() and notification->retryUntil() for retry timing

Sources: src/SendQueuedNotifications.php19-154


Component Interaction Architecture


Diagram: Core Component Relationships

This diagram shows how the three primary components interact with infrastructure services and delivery channels. The ChannelManager acts as the entry point, the NotificationSender orchestrates delivery, and SendQueuedNotifications enables asynchronous processing through the queue system. Channel instances are resolved from the container and invoked by the sender.

Sources: src/ChannelManager.php54-75 src/NotificationSender.php29-61 src/SendQueuedNotifications.php99-102


Notification Flow: Synchronous vs Asynchronous

The system supports two delivery paths: synchronous (immediate) and asynchronous (queued). The decision point occurs in NotificationSender->send().


Diagram: Synchronous and Asynchronous Notification Flow

This sequence diagram illustrates the dual-path architecture. The critical decision occurs at src/NotificationSender.php44-49 where the system checks if the notification implements ShouldQueue. For queued notifications, a SendQueuedNotifications job is created per notifiable-channel combination. The queue worker eventually calls back into ChannelManager->sendNow(), which bypasses the queue check and proceeds directly to channel delivery.

Sources: src/NotificationSender.php40-76 src/NotificationSender.php129-185 src/SendQueuedNotifications.php63-79 src/SendQueuedNotifications.php99-102


Channel Resolution and Driver Lifecycle

The ChannelManager resolves channel names to concrete implementations through a three-tier strategy:

  1. Custom creators: Registered via extend() method, checked first
  2. Built-in factory methods: Methods like createMailDriver(), createSlackDriver(), etc.
  3. Container resolution: Direct class lookup if channel name is a fully-qualified class name

Resolution Strategy


Diagram: Channel Driver Resolution Strategy

The resolution logic at src/ChannelManager.php122-156 implements this three-tier strategy. The $poolables array at src/ChannelManager.php44 currently contains ['slack'], meaning Slack channels are wrapped in connection pools. The pooling layer is provided by hypervel/object-pool and accessed through the HasPoolProxy trait.

Sources: src/ChannelManager.php122-156 src/ChannelManager.php44 src/ChannelManager.php163-170

Built-in Channels

Channel NameFactory MethodClassPooled
mailcreateMailDriver()MailChannelNo
broadcastcreateBroadcastDriver()BroadcastChannelNo
databasecreateDatabaseDriver()DatabaseChannelNo
slackcreateSlackDriver()SlackNotificationRouterChannelYes

Each factory method resolves the channel class from the container, allowing channels to declare their own dependencies for injection.

Sources: src/ChannelManager.php88-115


Context-Based Configuration

The ChannelManager uses Hyperf's Context system to store request-scoped configuration without global state mutation. This allows different execution contexts (e.g., HTTP requests, queue workers, console commands) to have independent configuration.

Context Keys

Context KeyDefaultSetter MethodGetter MethodPurpose
__notifications.defaultChannel'mail'deliverVia()getDefaultDriver()Override default channel per request
__notifications.defaultLocalenulllocale()getLocale()Set notification locale per request

Context Usage Pattern


Diagram: Context-Based Configuration Isolation

The context system ensures that ChannelManager->locale('es') in one request doesn't affect another concurrent request. The getters at src/ChannelManager.php195-232 always check the context first before falling back to instance properties.

Sources: src/ChannelManager.php195-232


Event Dispatching and Observability

The notification system dispatches events at key lifecycle points, enabling observability and extensibility through PSR-14 event listeners.

Lifecycle Events

Event ClassDispatch PointCancellableProperties
NotificationSendingBefore channel deliveryYes$notifiable, $notification, $channel
NotificationSentAfter successful deliveryNo$notifiable, $notification, $channel, $response
BroadcastNotificationCreatedBroadcast channel onlyNo (but is ShouldBroadcast)Notification data for broadcasting

Event Flow

The NotificationSending event serves as a gate mechanism. Event listeners can return false or call $event->shouldSend = false to prevent delivery. This is checked at src/NotificationSender.php113-124

Additionally, notification classes can implement their own shouldSend() method, which is checked before the event is dispatched at src/NotificationSender.php115-119

Cancellation Priority:

  1. notification->shouldSend($notifiable, $channel) (if method exists)
  2. NotificationSending event listeners

If either returns false, the notification is skipped for that channel.

Sources: src/NotificationSender.php93-124


Locale Handling

The system applies locale preferences in the following order of precedence:

  1. Explicit notification locale: $notification->locale property set on the notification instance
  2. Sender locale: Passed to NotificationSender constructor from ChannelManager->getLocale()
  3. Notifiable preference: If notifiable implements HasLocalePreference, call preferredLocale()
  4. System default: Falls back to framework default

The locale is applied at src/NotificationSender.php66-74 using the withLocale() method from the Localizable trait. This ensures translations and date formatting use the appropriate locale during notification rendering.

Sources: src/NotificationSender.php81-88 src/NotificationSender.php66-74


Notification ID Generation

Each notification receives a unique UUID identifier, generated at src/NotificationSender.php67 This ID is:

  • Persistent across channels: The same notification sent to multiple channels receives the same ID
  • Set on notification instance: Assigned to $notification->id if not already set
  • Used for tracking: Enables correlation of the same logical notification across different delivery channels

For queued notifications, the ID is generated during the queueing process at src/NotificationSender.php136 and attached to each job.

Sources: src/NotificationSender.php67 src/NotificationSender.php95-97 src/NotificationSender.php136 src/NotificationSender.php141-143


Summary of Key Classes

ClassFilePrimary Responsibility
ChannelManagersrc/ChannelManager.phpEntry point, driver factory, pooling, context management
NotificationSendersrc/NotificationSender.phpOrchestration, channel routing, locale handling, event dispatching
SendQueuedNotificationssrc/SendQueuedNotifications.phpQueue job wrapper, retry logic, failure handling
NotificationPoolProxysrc/NotificationPoolProxy.phpConnection pooling for expensive channels
AnonymousNotifiablesrc/AnonymousNotifiable.phpOn-demand routing for notifications without persistent entities

The architecture separates concerns clearly: ChannelManager provides infrastructure (driver resolution, pooling), NotificationSender implements business logic (routing, locale, events), and SendQueuedNotifications enables async processing. This separation allows each component to be tested and understood independently while working together to form a cohesive notification delivery system.

Sources: src/ChannelManager.php1-233 src/NotificationSender.php1-199 src/SendQueuedNotifications.php1-154