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
- Framework vs Application Boundaries
- Core Event Categories
- Authentication Events
- Security Events
- Session Analytics Events
- HTTP Events
- Database Events
- Cache Events
- Logging System Integration
- Event Listeners
- Creating Custom Events
- Extension Integration
- Performance Monitoring
- Best Practices
- Command Reference
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
- PSR-14 Compliance: Full compatibility with PSR-14 EventDispatcher standard
- Framework Features: All events automatically get event IDs, timestamps, and metadata support
- Event Propagation Control: Built-in support for stopping event propagation
- Future-Proof: Can change underlying implementation without breaking user code
- Consistency: Aligns with Glueful's philosophy of hiding implementation details
- 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
Glueful\Events\Auth
) 1. Authentication Events (
- User session lifecycle
- Authentication attempts and failures
- Rate limit violations (Auth)
Glueful\Events\Security
) 2. Security Events (
- CSRF protection failures
- Administrative/security violations
Glueful\Events\Http
) 3. HTTP Events (
- Request/response lifecycle
- HTTP authentication
- Exception handling
- Client interaction tracking
Glueful\Events\Database
) 4. Database Events (
- Query execution monitoring
- Entity lifecycle management
- Performance tracking
- Data modification auditing
Glueful\Events\Cache
) 5. Cache Events (
- 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.
Glueful\Events\Auth\AuthenticationFailedEvent
) 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()
Glueful\Events\Auth\SessionCreatedEvent
) SessionCreatedEvent (
When: New user session is established Purpose: Session analytics and initialization
- Namespace:
Glueful\Events\Auth\SessionCreatedEvent
- Access:
getSessionData()
,getUserUuid()
,getUsername()
,getTokens()
,getAccessToken()
,getRefreshToken()
Glueful\Events\Auth\SessionDestroyedEvent
) SessionDestroyedEvent (
When: User session is terminated Purpose: Cleanup and analytics
- Namespace:
Glueful\Events\Auth\SessionDestroyedEvent
- Access:
getAccessToken()
,getUserUuid()
,getReason()
,isExpired()
,isRevoked()
Security & Rate Limiting
Glueful\Events\Auth\RateLimitExceededEvent
) 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');
}
});
Glueful\Events\Security\CSRFViolationEvent
) 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()
- Access:
- EntityUpdatedEvent (
Glueful\Events\Database\EntityUpdatedEvent
)- Access:
getEntity()
,getTable()
,getChanges()
- Access:
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.