Cookbook

Events

Overview

The Glueful framework provides a comprehensive event system built on PSR-14 (Event Dispatcher), enabling decoupled communication between framework components and application code. The system clearly separates framework infrastructure concerns from application business logic through well-defined event boundaries.

Table of Contents

Architecture

Event Flow

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│  Framework      │───▶│  Event System    │───▶│  Application    │
│  Components     │    │  (Dispatcher)    │    │  Listeners      │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                              │
                              ▼
                       ┌─────────────────┐
                       │  Extensions     │
                       │  Listeners      │
                       └─────────────────┘

Key Features

  • Type-safe events with PHP 8.2+ readonly properties
  • Framework boundary separation for clear responsibility division
  • Performance monitoring built into event system
  • Extension integration with priority-based listeners
  • Logging system integration with structured context
  • Session analytics for user behavior tracking
  • Security event monitoring for threat detection

Event System Abstraction Layer

Glueful provides a complete abstraction layer over the underlying event system (PSR-14 EventDispatcher) to ensure framework consistency and future-proofing.

BaseEvent Class

All Glueful events extend the BaseEvent class which implements PSR-14's StoppableEventInterface:

<?php

namespace Glueful\Events\Contracts;

use Psr\EventDispatcher\StoppableEventInterface;

abstract class BaseEvent implements StoppableEventInterface
{
    private bool $stopped = false;
    /** @var array<string, mixed> */
    private array $metadata = [];
    private float $timestamp;
    private string $eventId;

    public function __construct()
    {
        $this->timestamp = microtime(true);
        $this->eventId = uniqid('evt_', true);
    }

    // PSR-14 propagation
    public function stopPropagation(): void { $this->stopped = true; }
    public function isPropagationStopped(): bool { return $this->stopped; }

    // Framework helpers
    public function setMetadata(string $key, mixed $value): void { $this->metadata[$key] = $value; }
    public function getMetadata(?string $key = null): mixed { return $key === null ? $this->metadata : ($this->metadata[$key] ?? null); }
    public function getTimestamp(): float { return $this->timestamp; }
    public function getEventId(): string { return $this->eventId; }
    public function getName(): string { return static::class; }
}

Benefits of the Abstraction Layer

  1. PSR-14 Compliance: Full compatibility with PSR-14 EventDispatcher standard
  2. Framework Features: All events automatically get event IDs, timestamps, and metadata support
  3. Event Propagation Control: Built-in support for stopping event propagation
  4. Future-Proof: Can change underlying implementation without breaking user code
  5. Consistency: Aligns with Glueful's philosophy of hiding implementation details
  6. Extensibility: Easy to add new framework-wide features to all events

Creating Custom Events

When creating custom events, always extend BaseEvent:

<?php

declare(strict_types=1);

namespace App\Events;

use Glueful\Events\Contracts\BaseEvent;

class OrderShippedEvent extends BaseEvent
{
    public function __construct(
        public readonly string $orderId,
        public readonly string $trackingNumber
    ) {
        parent::__construct(); // Initialize BaseEvent features
        
        // Set metadata using BaseEvent's functionality
        $this->setMetadata('source', 'order_service');
        $this->setMetadata('priority', 'high');
    }
}

Enhanced Event Dispatching

Events are dispatched using the PSR-14 compliant Event facade:

use Glueful\Events\Event;

$event = new OrderShippedEvent('123', 'TRACK456');
Event::dispatch($event);

// Framework automatically tracks:
// - Event ID: evt_64f1a2b3c4d5e
// - Event Name: App\Events\OrderShippedEvent
// - Timestamp: 1693234567.123
// - Any custom metadata
// - Propagation control (if event is stopped)

Event Facade API

The Event facade provides these methods:

use Glueful\Events\Event;

// Dispatch an event
$result = Event::dispatch($event);

// Register listeners with optional priority
Event::listen(UserLoginEvent::class, $callable, $priority = 0);
Event::listen(UserLoginEvent::class, '@service:method', 10);

// Register subscriber classes
Event::subscribe(UserEventSubscriber::class);

// Check for listeners
$hasListeners = Event::hasListeners(UserLoginEvent::class);
$listeners = Event::getListeners(UserLoginEvent::class);

Framework vs Application Boundaries

Framework Responsibilities

The framework emits events for infrastructure and protocol concerns:

  • HTTP protocol validation (auth headers, CSRF tokens)
  • Rate limiting enforcement
  • Database query execution
  • Cache operations
  • Session lifecycle management
  • Security policy violations

Application Responsibilities

Applications listen to framework events and implement business logic responses:

  • User behavior analytics
  • Business rule enforcement
  • Custom security policies
  • Audit trail creation
  • Notification dispatch
  • Integration with external services

Boundary Example

// Framework: Detects rate limit violation (infrastructure concern)
use Glueful\Events\Auth\RateLimitExceededEvent;
use Glueful\Events\Event;

Event::dispatch(new RateLimitExceededEvent(
    clientIp: $ip,
    rule: 'default_limit',
    currentCount: $count,
    limit: $limit,
    windowSeconds: $window,
));

// Application: Responds with business logic
Event::listen(RateLimitExceededEvent::class, function(RateLimitExceededEvent $event) {
    if ($event->isSevereViolation()) {
        $this->security->temporaryBlockIp($event->getClientIp());
        $this->alerts->critical('Severe rate limit violation', ['ip' => $event->getClientIp()]);
    }
});

Core Event Categories

1. Authentication Events (Glueful\Events\Auth)

  • User session lifecycle
  • Authentication attempts and failures
  • Rate limit violations (Auth)

2. Security Events (Glueful\Events\Security)

  • CSRF protection failures
  • Administrative/security violations

3. HTTP Events (Glueful\Events\Http)

  • Request/response lifecycle
  • HTTP authentication
  • Exception handling
  • Client interaction tracking

4. Database Events (Glueful\Events\Database)

  • Query execution monitoring
  • Entity lifecycle management
  • Performance tracking
  • Data modification auditing

5. Cache Events (Glueful\Events\Cache)

  • Cache operations (hit/miss/invalidation)
  • Performance monitoring
  • Cache strategy optimization

Authentication Events

Note: The following events exist in core. A UserAuthenticatedEvent is not currently provided by the framework.

AuthenticationFailedEvent (Glueful\Events\Auth\AuthenticationFailedEvent)

When: Authentication attempt fails Purpose: Security monitoring and user experience improvement

  • Namespace: Glueful\Events\Auth\AuthenticationFailedEvent
  • Access: getUsername(), getReason(), getClientIp(), getUserAgent(), isInvalidCredentials(), isUserDisabled(), isSuspicious()

SessionCreatedEvent (Glueful\Events\Auth\SessionCreatedEvent)

When: New user session is established Purpose: Session analytics and initialization

  • Namespace: Glueful\Events\Auth\SessionCreatedEvent
  • Access: getSessionData(), getUserUuid(), getUsername(), getTokens(), getAccessToken(), getRefreshToken()

SessionDestroyedEvent (Glueful\Events\Auth\SessionDestroyedEvent)

When: User session is terminated Purpose: Cleanup and analytics

  • Namespace: Glueful\Events\Auth\SessionDestroyedEvent
  • Access: getAccessToken(), getUserUuid(), getReason(), isExpired(), isRevoked()

Security & Rate Limiting

RateLimitExceededEvent (Glueful\Events\Auth\RateLimitExceededEvent)

When: Rate limit is exceeded Purpose: Security monitoring and adaptive responses

  • Namespace: Glueful\Events\Auth\RateLimitExceededEvent
  • Access: getClientIp(), getRule(), getCurrentCount(), getLimit(), getWindowSeconds(), getExcessCount(), getExcessPercentage(), isSevereViolation()

Usage Example:

use Glueful\Events\Event;

Event::listen(RateLimitExceededEvent::class, function(RateLimitExceededEvent $event) {
    if ($event->isSevereViolation()) {
        $this->securityManager->blockIP($event->getClientIp(), '1 hour');
    } else {
        $this->rateLimiter->penaltyMode($event->getClientIp(), '10 minutes');
    }
});

CSRFViolationEvent (Glueful\Events\Security\CSRFViolationEvent)

When: CSRF token validation fails Purpose: Security monitoring and attack prevention

  • Namespace: Glueful\Events\Security\CSRFViolationEvent
  • Access: reason, request (Symfony Request)

Session Analytics Events

SessionActivityEvent

When: User performs actions during session Purpose: User behavior analytics and experience optimization

namespace Glueful\Events\Analytics;

use Glueful\Events\Contracts\BaseEvent;

class SessionActivityEvent extends BaseEvent
{
    public function __construct(
        public readonly string $sessionId,
        public readonly string $userId,
        public readonly string $action,
        public readonly string $resource,
        public readonly array $parameters = [],
        public readonly float $responseTime = 0.0,
        public readonly array $metadata = []
    ) {}
    
    public function isSlowResponse(): bool
    {
        return $this->responseTime > 2.0;
    }
    
    public function isErrorResponse(): bool
    {
        return isset($this->metadata['status_code']) && 
               $this->metadata['status_code'] >= 400;
    }
}

Usage Example:

use Glueful\Events\Event;

Event::listen(SessionActivityEvent::class, function(SessionActivityEvent $event) {
    // User behavior analytics
    $this->analyticsService->recordUserAction([
        'user_id' => $event->userId,
        'action' => $event->action,
        'resource' => $event->resource,
        'response_time' => $event->responseTime,
        'timestamp' => now()->toISOString()
    ]);
    
    // Performance monitoring
    if ($event->isSlowResponse()) {
        $this->performanceMonitor->recordSlowAction($event);
    }
    
    // User experience optimization
    if ($event->isErrorResponse()) {
        $this->uxAnalyzer->recordErrorPattern($event);
    }
});

SessionPatternEvent

When: Session usage patterns are analyzed Purpose: Advanced analytics and personalization

namespace Glueful\Events\Analytics;

use Glueful\Events\Contracts\BaseEvent;

class SessionPatternEvent extends BaseEvent
{
    public function __construct(
        public readonly string $userId,
        public readonly string $patternType, // usage, navigation, preference
        public readonly array $pattern,
        public readonly float $confidence,
        public readonly array $recommendations = []
    ) {}
    
    public function isHighConfidence(): bool
    {
        return $this->confidence >= 0.85;
    }
}

HTTP Events

RequestReceivedEvent

When: HTTP request is received and parsed Purpose: Request tracking and analysis

namespace Glueful\Events\Http;

use Glueful\Events\Contracts\BaseEvent;

class RequestReceivedEvent extends BaseEvent
{
    public function __construct(
        public readonly Request $request,
        public readonly float $timestamp,
        public readonly array $metadata = []
    ) {}
    
    public function isAPIRequest(): bool
    {
        return str_starts_with($this->request->getPathInfo(), '/api/');
    }
    
    public function isSecure(): bool
    {
        return $this->request->isSecure();
    }
    
    public function getEndpoint(): string
    {
        return $this->request->getMethod() . ' ' . $this->request->getPathInfo();
    }
}

ResponseSentEvent

When: HTTP response is sent to client Purpose: Performance monitoring and analytics

namespace Glueful\Events\Http;

use Glueful\Events\Contracts\BaseEvent;

class ResponseSentEvent extends BaseEvent
{
    public function __construct(
        public readonly Request $request,
        public readonly Response $response,
        public readonly float $processingTime,
        public readonly array $metrics = []
    ) {}
    
    public function isError(): bool
    {
        return $this->response->getStatusCode() >= 400;
    }
    
    public function isSlowResponse(): bool
    {
        return $this->processingTime > 1.0;
    }
    
    public function getStatusCode(): int
    {
        return $this->response->getStatusCode();
    }
}

Database Events

QueryExecutedEvent

When: Database query is executed Purpose: Performance monitoring and audit

  • Namespace: Glueful\Events\Database\QueryExecutedEvent
  • Access: getSql(), getBindings(), getExecutionTime(), getConnectionName(), isSlow(), getQueryType(), isModifying()

Usage Example:

use Glueful\Events\Event;

Event::listen(QueryExecutedEvent::class, function(QueryExecutedEvent $event) {
    // Performance monitoring
    if ($event->isSlow()) {
        $this->performanceLogger->warning('Slow query detected', [
            'query' => $event->getSql(),
            'execution_time' => $event->getExecutionTime(),
            'type' => $event->getQueryType(),
            'connection' => $event->getConnectionName(),
        ]);
    }
    
    // Audit trail for data modifications
    if ($event->isModifying()) {
        $this->auditService->logDataChange([
            'operation' => $event->getQueryType(),
            'query' => $event->getSql(),
            'connection' => $event->getConnectionName(),
            'timestamp' => now()->toISOString()
        ]);
    }
    
    // Query analytics
    $this->queryAnalytics->recordQuery($event);
});

EntityCreatedEvent and EntityUpdatedEvent

Core exposes separate events for lifecycle changes:

  • EntityCreatedEvent (Glueful\Events\Database\EntityCreatedEvent)
    • Access: getEntity(), getTable()
  • EntityUpdatedEvent (Glueful\Events\Database\EntityUpdatedEvent)
    • Access: getEntity(), getTable(), getChanges()

Cache Events

Cache operations are modeled as distinct events in core (use these in listeners/examples):

  • Glueful\Events\Cache\CacheHitEvent
  • Glueful\Events\Cache\CacheMissEvent
  • Glueful\Events\Cache\CacheInvalidatedEvent

Usage Example:

use Glueful\Events\Event;

use Glueful\Events\Cache\CacheHitEvent;
use Glueful\Events\Cache\CacheMissEvent;

Event::listen(CacheHitEvent::class, function(CacheHitEvent $event) {
    if ($event->isSlow(0.1)) {
        $this->logger->warning('Slow cache retrieval', [
            'key' => $event->getKey(),
            'time' => $event->getRetrievalTime(),
            'size' => $event->getValueSize(),
        ]);
    }
});

Event::listen(CacheMissEvent::class, function(CacheMissEvent $event) {
    $this->analytics->recordCacheMiss($event->getKey());
});

Logging System Integration

Structured Event Logging

The framework provides seamless integration with the logging system through event-driven structured logging:

namespace Glueful\Logging;

class EventLoggerListener
{
    public function __construct(
        private LoggerInterface $logger,
        private ContextEnricher $contextEnricher
    ) {}
    
    public function logAuthenticationEvent(UserAuthenticatedEvent $event): void
    {
        $context = $this->contextEnricher->enrich([
            'event_type' => 'authentication',
            'user_id' => $event->userId,
            'auth_method' => $event->authMethod,
            'client_ip' => $event->clientIp,
            'user_agent' => $event->userAgent,
            'session_data' => $event->sessionData
        ]);
        
        $this->logger->info('User authenticated successfully', $context);
    }
    
    public function logSecurityEvent(RateLimitExceededEvent $event): void
    {
        $context = $this->contextEnricher->enrich([
            'event_type' => 'security_violation',
            'violation_type' => 'rate_limit_exceeded',
            'client_ip' => $event->clientIp,
            'endpoint' => $event->endpoint,
            'current_count' => $event->currentCount,
            'limit_config' => $event->limitConfig,
            'severity' => $event->isSeverViolation() ? 'high' : 'medium'
        ]);
        
        $this->logger->warning('Rate limit exceeded', $context);
    }
    
    public function logPerformanceEvent(QueryExecutedEvent $event): void
    {
        if ($event->isSlow()) {
            $context = $this->contextEnricher->enrich([
                'event_type' => 'performance',
                'component' => 'database',
                'query_type' => $event->getQueryType(),
                'execution_time' => $event->executionTime,
                'affected_table' => $event->getAffectedTable(),
                'query_hash' => hash('sha256', $event->sql)
            ]);
            
            $this->logger->warning('Slow database query detected', $context);
        }
    }
}

Context Enrichment

namespace Glueful\Logging;

class ContextEnricher
{
    public function enrich(array $context): array
    {
        return array_merge($context, [
            'timestamp' => now()->toISOString(),
            'request_id' => $this->getRequestId(),
            'trace_id' => $this->getTraceId(),
            'user_session' => $this->getCurrentSession(),
            'environment' => config('app.env'),
            'version' => config('app.version')
        ]);
    }
    
    private function getRequestId(): ?string
    {
        return request()?->headers->get('X-Request-ID');
    }
    
    private function getTraceId(): ?string
    {
        return request()?->headers->get('X-Trace-ID');
    }
    
    private function getCurrentSession(): ?array
    {
        $session = session();
        return $session ? [
            'id' => $session->getId(),
            'user_id' => $session->get('user_id')
        ] : null;
    }
}

Event Listeners

Built-in Framework Listeners

SecurityMonitoringListener

namespace Glueful\Events\Listeners;

class SecurityMonitoringListener
{
    public function __construct(
        private SecurityAnalyzer $analyzer,
        private ThreatDetector $threatDetector,
        private AlertService $alertService
    ) {}
    
    public function onAuthenticationFailed(AuthenticationFailedEvent $event): void
    {
        $this->analyzer->recordFailedAttempt($event);
        
        if ($this->threatDetector->isBruteForcePattern($event)) {
            $this->alertService->securityAlert('brute_force_detected', $event);
        }
    }
    
    public function onRateLimitExceeded(RateLimitExceededEvent $event): void
    {
        $this->analyzer->recordRateLimitViolation($event);
        
        if ($event->isSeverViolation()) {
            $this->alertService->criticalAlert('severe_rate_limit_violation', $event);
        }
    }
    
    public function onSuspiciousActivity(SuspiciousActivityDetectedEvent $event): void
    {
        if ($event->isHighRisk()) {
            $this->alertService->securityAlert('high_risk_activity', $event);
            $this->analyzer->initiateDetailedAnalysis($event);
        }
    }
}

PerformanceMonitoringListener

namespace Glueful\Events\Listeners;

class PerformanceMonitoringListener
{
    public function __construct(
        private MetricsCollector $metrics,
        private PerformanceAnalyzer $analyzer,
        private AlertService $alertService
    ) {}
    
    public function onQueryExecuted(QueryExecutedEvent $event): void
    {
        $this->metrics->recordQueryMetrics([
            'execution_time' => $event->executionTime,
            'query_type' => $event->getQueryType(),
            'table' => $event->getAffectedTable()
        ]);
        
        if ($event->isSlow()) {
            $this->analyzer->analyzeSlowQuery($event);
        }
    }
    
    public function onResponseSent(ResponseSentEvent $event): void
    {
        $this->metrics->recordResponseMetrics([
            'processing_time' => $event->processingTime,
            'status_code' => $event->getStatusCode(),
            'endpoint' => $event->request->getPathInfo()
        ]);
        
        if ($event->isSlowResponse()) {
            $this->analyzer->analyzeSlowResponse($event);
        }
    }
    
    public function onCacheOperation(CacheOperationEvent $event): void
    {
        $this->metrics->recordCacheMetrics([
            'operation' => $event->operation,
            'hit' => $event->isHit(),
            'operation_time' => $event->operationTime
        ]);
    }
}

AnalyticsListener

namespace Glueful\Events\Listeners;

class AnalyticsListener
{
    public function __construct(
        private AnalyticsService $analytics,
        private UserBehaviorTracker $behaviorTracker
    ) {}
    
    public function onUserAuthenticated(UserAuthenticatedEvent $event): void
    {
        $this->analytics->recordEvent('user_login', [
            'user_id' => $event->userId,
            'method' => $event->authMethod,
            'timestamp' => now()->toISOString()
        ]);
    }
    
    public function onSessionActivity(SessionActivityEvent $event): void
    {
        $this->behaviorTracker->recordActivity([
            'user_id' => $event->userId,
            'action' => $event->action,
            'resource' => $event->resource,
            'response_time' => $event->responseTime
        ]);
    }
    
    public function onSessionDestroyed(SessionDestroyedEvent $event): void
    {
        $this->analytics->recordEvent('session_ended', [
            'user_id' => $event->userId,
            'duration' => $event->duration,
            'reason' => $event->reason
        ]);
    }
}

Custom Application Listeners

// In your application
class MyApplicationEventListener
{
    public function __construct(
        private BusinessAnalytics $analytics,
        private NotificationService $notifications
    ) {}
    
    public function onUserAuthenticated(UserAuthenticatedEvent $event): void
    {
        // Business-specific login tracking
        $this->analytics->userLoggedIn($event->userId);
        
        // Send welcome notification for first-time users
        if ($this->isFirstLogin($event->userId)) {
            $this->notifications->sendWelcomeMessage($event->userId);
        }
    }
    
    public function onEntityCreated(EntityLifecycleEvent $event): void
    {
        // Business workflow triggers
        if ($event->entityType === 'order' && $event->isCreated()) {
            $this->processNewOrder($event->entityId, $event->entityData);
        }
    }
    
    private function isFirstLogin(string $userId): bool
    {
        return $this->analytics->getLoginCount($userId) === 1;
    }
    
    private function processNewOrder(string $orderId, array $orderData): void
    {
        // Business logic for new orders
        $this->notifications->notifyWarehouse($orderId);
        $this->analytics->recordSale($orderData);
    }
}

Creating Custom Events

Step 1: Define Event Class

<?php

declare(strict_types=1);

namespace App\Events;

use Glueful\Events\Contracts\BaseEvent;

class OrderShippedEvent extends BaseEvent
{
    public function __construct(
        public readonly string $orderId,
        public readonly string $customerId,
        public readonly string $trackingNumber,
        public readonly string $carrier,
        public readonly array $items,
        public readonly array $shippingAddress,
        public readonly float $shippingCost,
        array $metadata = []
    ) {
        parent::__construct(); // Initialize BaseEvent features
        
        // Set metadata using BaseEvent's functionality
        foreach ($metadata as $key => $value) {
            $this->setMetadata($key, $value);
        }
    }
    
    public function getItemCount(): int
    {
        return count($this->items);
    }
    
    public function isExpressShipping(): bool
    {
        return ($this->getMetadata('shipping_method') ?? '') === 'express';
    }
    
    public function isInternational(): bool
    {
        return ($this->shippingAddress['country'] ?? 'US') !== 'US';
    }
}

Step 2: Dispatch Event

namespace App\Services;

use Glueful\Events\Event;
use App\Events\OrderShippedEvent;

class OrderService
{
    public function shipOrder(string $orderId): void
    {
        $order = $this->getOrder($orderId);
        
        // Ship the order
        $trackingNumber = $this->shippingProvider->ship($order);
        
        // Update order status
        $this->updateOrderStatus($orderId, 'shipped');
        
        // Dispatch event
        $event = new OrderShippedEvent(
            orderId: $orderId,
            customerId: $order['customer_id'],
            trackingNumber: $trackingNumber,
            carrier: $order['shipping_carrier'],
            items: $order['items'],
            shippingAddress: $order['shipping_address'],
            shippingCost: $order['shipping_cost'],
            metadata: [
                'shipping_method' => $order['shipping_method'],
                'shipped_at' => now()->toISOString()
            ]
        );
        
        Event::dispatch($event);
    }
}

Step 3: Create Listeners

// Service for handling shipping notifications
class ShippingNotificationService
{
    public function onOrderShipped(OrderShippedEvent $event): void
    {
        // Send tracking email to customer
        $this->emailService->sendTrackingEmail(
            $event->customerId,
            $event->orderId,
            $event->trackingNumber,
            $event->carrier
        );
        
        // Send SMS for express shipping
        if ($event->isExpressShipping()) {
            $this->smsService->sendShippingAlert(
                $event->customerId,
                $event->trackingNumber
            );
        }
    }
}

// Analytics service
class ShippingAnalyticsService
{
    public function onOrderShipped(OrderShippedEvent $event): void
    {
        $this->analytics->recordShipping([
            'order_id' => $event->orderId,
            'carrier' => $event->carrier,
            'item_count' => $event->getItemCount(),
            'shipping_cost' => $event->shippingCost,
            'is_express' => $event->isExpressShipping(),
            'is_international' => $event->isInternational(),
            'timestamp' => now()->toISOString()
        ]);
    }
}

Extension Integration

Registering Event Listeners in Extensions

namespace Glueful\Extensions\MyExtension;

use Glueful\Extensions\BaseExtension;
use Glueful\Events\Event;
use Glueful\Events\EventSubscriberInterface;

class Extension extends BaseExtension
{
    public function boot(): void
    {
        // Register event subscriber
        Event::subscribe(ExtensionEventSubscriber::class);

        // Or register individual listeners
        Event::listen(UserAuthenticatedEvent::class, '@extension_analytics:trackLogin', 10);
        Event::listen(QueryExecutedEvent::class, [$this, 'onQueryExecuted'], 5);
    }

    public function onQueryExecuted(QueryExecutedEvent $event): void
    {
        // Extension-specific database monitoring
        if ($event->isSlow()) {
            $this->alertSlowQuery($event);
        }
    }
}

class ExtensionEventSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            UserAuthenticatedEvent::class => 'onUserAuthenticated',
            RateLimitExceededEvent::class => ['onRateLimitExceeded', 100]
        ];
    }

    public function onUserAuthenticated(UserAuthenticatedEvent $event): void
    {
        // Extension-specific logic
    }

    public function onRateLimitExceeded(RateLimitExceededEvent $event): void
    {
        // Extension-specific rate limit handling
    }
}

Event Listener Registration Patterns

Option 1: Direct Registration with Event::listen()

namespace App\Listeners;

use Glueful\Events\Event;
use Glueful\Events\Auth\UserAuthenticatedEvent;

class SimpleAuthListener
{
    public function register(): void
    {
        // Register a single listener with priority
        Event::listen(UserAuthenticatedEvent::class, [$this, 'handleLogin'], 10);

        // Register using container service reference (lazy loading)
        Event::listen(UserAuthenticatedEvent::class, '@analytics_service:trackLogin', 5);

        // Register a closure
        Event::listen(UserAuthenticatedEvent::class, function(UserAuthenticatedEvent $event) {
            // Handle event inline
        });
    }

    public function handleLogin(UserAuthenticatedEvent $event): void
    {
        // Handle the authentication event
    }
}

Option 2: Event Subscriber Pattern (for multiple events)

namespace Glueful\Extensions\MyExtension;

use Glueful\Events\EventSubscriberInterface;
use Glueful\Events\Auth\UserAuthenticatedEvent;
use Glueful\Events\Database\QueryExecutedEvent;
use Glueful\Events\Auth\RateLimitExceededEvent;

class MyEventSubscriber implements EventSubscriberInterface
{
    /**
     * Define subscribed events and their handlers
     * This pattern is ideal when a single class handles multiple events
     */
    public static function getSubscribedEvents(): array
    {
        return [
            UserAuthenticatedEvent::class => [
                ['onUserAuthenticated', 10],  // Priority 10
                ['logAuthentication', 0]       // Priority 0
            ],
            QueryExecutedEvent::class => 'onQueryExecuted',
            RateLimitExceededEvent::class => ['onRateLimitExceeded', 100]
        ];
    }
    
    public function onUserAuthenticated(UserAuthenticatedEvent $event): void
    {
        // Handle authentication
    }
    
    public function logAuthentication(UserAuthenticatedEvent $event): void
    {
        // Log authentication
    }
    
    public function onQueryExecuted(QueryExecutedEvent $event): void
    {
        // Handle query execution
    }
    
    public function onRateLimitExceeded(RateLimitExceededEvent $event): void
    {
        // Handle rate limit exceeded
    }
}

Registering Subscribers

use Glueful\Events\Event;

// Register the subscriber
Event::subscribe(MyEventSubscriber::class);

Note: The Event Subscriber pattern (using EventSubscriberInterface) is preferred when:

  • A single class needs to handle multiple events
  • You want to define all event mappings in one place
  • You need different priorities for different handlers
  • Following the pattern used by framework listeners

Performance Monitoring

Event Performance Metrics

namespace Glueful\Events\Monitoring;

class EventPerformanceMonitor
{
    private array $metrics = [];
    
    public function startTiming(string $eventClass): void
    {
        $this->metrics[$eventClass]['start'] = microtime(true);
    }
    
    public function endTiming(string $eventClass): void
    {
        if (isset($this->metrics[$eventClass]['start'])) {
            $duration = microtime(true) - $this->metrics[$eventClass]['start'];
            $this->metrics[$eventClass]['durations'][] = $duration;
            
            if ($duration > 0.1) { // 100ms threshold
                $this->logSlowEventProcessing($eventClass, $duration);
            }
        }
    }
    
    public function getAverageTime(string $eventClass): float
    {
        $durations = $this->metrics[$eventClass]['durations'] ?? [];
        return count($durations) > 0 ? array_sum($durations) / count($durations) : 0.0;
    }
    
    private function logSlowEventProcessing(string $eventClass, float $duration): void
    {
        logger()->warning('Slow event processing detected', [
            'event_class' => $eventClass,
            'duration' => $duration,
            'threshold' => 0.1
        ]);
    }
}

Memory Usage Monitoring

namespace Glueful\Events\Monitoring;

class EventMemoryMonitor
{
    private int $baselineMemory;
    
    public function __construct()
    {
        $this->baselineMemory = memory_get_usage(true);
    }
    
    public function checkMemoryUsage(string $eventClass): void
    {
        $currentMemory = memory_get_usage(true);
        $memoryIncrease = $currentMemory - $this->baselineMemory;
        
        // Alert if memory increase is significant (> 10MB)
        if ($memoryIncrease > 10 * 1024 * 1024) {
            logger()->warning('High memory usage during event processing', [
                'event_class' => $eventClass,
                'memory_increase' => $memoryIncrease,
                'current_memory' => $currentMemory,
                'peak_memory' => memory_get_peak_usage(true)
            ]);
        }
    }
}

Best Practices

1. Event Design

✅ Good Event Design:

class UserProfileUpdatedEvent extends BaseEvent
{
    public function __construct(
        public readonly string $userId,
        public readonly array $changes,
        public readonly array $previousData,
        public readonly ?string $updatedBy = null,
        public readonly array $metadata = []
    ) {}
    
    public function hasEmailChanged(): bool
    {
        return isset($this->changes['email']);
    }
    
    public function wasProfilePictureUpdated(): bool
    {
        return isset($this->changes['profile_picture']);
    }
}

❌ Poor Event Design:

class UserEvent extends BaseEvent
{
    public $data; // Not readonly, not typed
    public $action; // Unclear what this represents
    
    public function __construct($data, $action)
    {
        $this->data = $data;
        $this->action = $action;
    }
}

2. Listener Implementation

✅ Good Listener:

class UserNotificationListener
{
    public function onUserProfileUpdated(UserProfileUpdatedEvent $event): void
    {
        try {
            if ($event->hasEmailChanged()) {
                $this->sendEmailChangeConfirmation($event->userId);
            }
            
            if ($event->wasProfilePictureUpdated()) {
                $this->updateProfilePictureCache($event->userId);
            }
        } catch (Exception $e) {
            logger()->error('Failed to process user profile update', [
                'user_id' => $event->userId,
                'error' => $e->getMessage()
            ]);
            // Don't re-throw - listener failures shouldn't break main flow
        }
    }
}

❌ Poor Listener:

class BadListener
{
    public function handleEvent($event): void
    {
        // No type hints
        // Doing too much in one listener
        $this->sendEmail($event->data);
        $this->updateDatabase($event->data);
        $this->callExternalAPI($event->data);
        $this->generateReport($event->data);
        // No error handling
    }
}

3. Framework vs Application Separation

✅ Proper Separation:

// Framework: Detects and reports security violation
use Glueful\Events\Event;

class SecurityMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        if ($this->rateLimiter->isExceeded($request->getClientIp())) {
            $event = new RateLimitExceededEvent(/* ... */);
            Event::dispatch($event);
            return new Response('Rate limit exceeded', 429);
        }
        
        return $next($request);
    }
}

// Application: Responds with business logic
class BusinessSecurityListener
{
    public function onRateLimitExceeded(RateLimitExceededEvent $event): void
    {
        // Business decision: How to respond to this violation
        if ($this->isTrustedClient($event->clientIp)) {
            // Increase limits for trusted clients
            $this->rateLimiter->increaseLimit($event->clientIp);
        } else {
            // Apply business-specific penalties
            $this->applySecurityPenalty($event);
        }
    }
}

4. Performance Considerations

// ✅ Lightweight event processing
class PerformantListener
{
    public function onHeavyEvent(HeavyEvent $event): void
    {
        // Queue heavy processing instead of doing it synchronously
        $this->queue->push(ProcessHeavyEventJob::class, [
            'event_data' => $event->getEventData()
        ]);
    }
}

// ✅ Conditional processing
class ConditionalListener
{
    public function onFrequentEvent(FrequentEvent $event): void
    {
        // Only process if certain conditions are met
        if ($event->requiresProcessing()) {
            $this->doProcessing($event);
        }
    }
}

Command Reference

Creating Events and Listeners

# Create a new event class
php glueful event:create OrderShippedEvent

# Create an event listener
php glueful event:listener OrderShippedListener

# Note: The following commands are available today
php glueful event:create OrderShippedEvent
php glueful event:listener OrderShippedListener

# The following commands are not implemented in core yet and are subject to change
# php glueful event:subscriber OrderEventSubscriber
# php glueful event:list
# php glueful event:debug UserAuthenticatedEvent

Event System Diagnostics

# Planned diagnostics (not currently implemented)
# php glueful event:monitor --duration=60s
# php glueful event:stats
# php glueful event:test UserAuthenticatedEvent
# php glueful event:validate

This comprehensive event system documentation provides the foundation for building robust, maintainable applications with clear separation between framework infrastructure and application business logic. The event-driven architecture enables powerful integration patterns while maintaining performance and reliability.