Skip to main content
Lyger provides a powerful event system for decoupling application logic and enabling real-time features. Events can be dispatched, listened to, and broadcast across channels.

Creating Events

Events extend the base Event class and contain data about what happened:
use Lyger\Events\Event;

class UserRegistered extends Event
{
    public function __construct(
        public readonly int $userId,
        public readonly string $email,
        public readonly string $name
    ) {}
}

class OrderPlaced extends Event
{
    public function __construct(
        public readonly int $orderId,
        public readonly float $total,
        public readonly array $items
    ) {}
}
Events automatically get their name from the class name. UserRegistered becomes “UserRegistered” in the event system.

Dispatching Events

Trigger events when important actions occur:
use Lyger\Events\EventDispatcher;

// Dispatch an event
$event = new UserRegistered(
    userId: 123,
    email: 'user@example.com',
    name: 'John Doe'
);

EventDispatcher::dispatch($event);

// Dispatch with additional payload
EventDispatcher::dispatch($event, [
    'ip_address' => $_SERVER['REMOTE_ADDR'],
    'timestamp' => time()
]);

Event Listeners

Register listeners to respond to events:

Basic Listener

use Lyger\Events\EventDispatcher;

EventDispatcher::listen('UserRegistered', function($event, $payload) {
    // Send welcome email
    sendWelcomeEmail($event->email, $event->name);
    
    // Log registration
    logger()->info("User {$event->userId} registered");
    
    // Track analytics
    analytics()->track('user_registered', [
        'user_id' => $event->userId,
        'email' => $event->email
    ]);
});

Multiple Listeners

Multiple listeners can respond to the same event:
// Listener 1: Send email
EventDispatcher::listen('UserRegistered', function($event) {
    sendWelcomeEmail($event->email, $event->name);
});

// Listener 2: Create profile
EventDispatcher::listen('UserRegistered', function($event) {
    createUserProfile($event->userId);
});

// Listener 3: Award points
EventDispatcher::listen('UserRegistered', function($event) {
    awardSignupBonus($event->userId, 100);
});

Wildcard Listeners

Listen to multiple events with pattern matching:
// Listen to all user events
EventDispatcher::listen('User*', function($event, $payload) {
    logger()->info("User event: " . $event->getName());
});

// Listen to all order events
EventDispatcher::listen('Order*', function($event) {
    updateDashboard($event);
});

// Listen to all events
EventDispatcher::listen('*', function($event) {
    auditLog($event);
});
Wildcard listeners run for every matching event. Use them sparingly to avoid performance issues.

Event Service Provider

Organize event listeners in a service provider:
use Lyger\Events\EventServiceProvider;

class AppEventServiceProvider extends EventServiceProvider
{
    public function register(): void
    {
        // User events
        $this->listen('UserRegistered', function($event) {
            sendWelcomeEmail($event->email);
        });
        
        $this->listen('UserLoggedIn', function($event) {
            updateLastLogin($event->userId);
        });
        
        // Order events
        $this->listen('OrderPlaced', function($event) {
            processOrder($event->orderId);
        });
        
        $this->listen('OrderShipped', function($event) {
            notifyCustomer($event->orderId);
        });
    }
}

// Bootstrap
$provider = new AppEventServiceProvider();
$provider->register();

Broadcasting Events

Broadcast events to WebSocket channels for real-time updates:

Broadcastable Events

Implement the ShouldBroadcast interface:
use Lyger\Events\Event;
use Lyger\Events\ShouldBroadcast;
use Lyger\Events\Channel;
use Lyger\Events\PrivateChannel;

class MessageSent extends Event implements ShouldBroadcast
{
    public function __construct(
        public readonly int $chatId,
        public readonly int $userId,
        public readonly string $message
    ) {}
    
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel("chat.{$this->chatId}")
        ];
    }
    
    public function broadcastAs(): string
    {
        return 'message.sent';
    }
    
    public function broadcastWith(): array
    {
        return [
            'chat_id' => $this->chatId,
            'user_id' => $this->userId,
            'message' => $this->message,
            'timestamp' => time()
        ];
    }
}

Broadcasting Manually

use Lyger\Events\BroadcastManager;
use Lyger\Events\Channel;

// Broadcast to a channel
BroadcastManager::send(
    [new Channel('notifications')],
    'notification.received',
    ['title' => 'New message', 'body' => 'You have 3 unread messages']
);

// Broadcast event
$event = new MessageSent(123, 456, 'Hello!');
BroadcastManager::event($event)
    ->on(new Channel('chat.123'))
    ->send();

Channel Types

Lyger supports different channel types for broadcasting:

Public Channel

use Lyger\Events\Channel;

// Anyone can subscribe
$channel = new Channel('announcements');
$channel = new Channel('public.updates');

Private Channel

use Lyger\Events\PrivateChannel;

// Requires authentication
$channel = new PrivateChannel("user.{$userId}");
$channel = new PrivateChannel("chat.{$chatId}");

Presence Channel

use Lyger\Events\PresenceChannel;

// Track who's subscribed
$channel = new PresenceChannel('chat.123');
$channel = new PresenceChannel('game.room.456');

Real-World Examples

User Activity Tracking

// Define events
class UserLoggedIn extends Event {
    public function __construct(public int $userId, public string $ip) {}
}

class UserLoggedOut extends Event {
    public function __construct(public int $userId) {}
}

// Register listeners
EventDispatcher::listen('UserLoggedIn', function($event) {
    // Update last login
    User::find($event->userId)->update([
        'last_login' => date('Y-m-d H:i:s'),
        'last_ip' => $event->ip
    ]);
    
    // Track session
    session()->set('logged_in_at', time());
});

EventDispatcher::listen('UserLoggedOut', function($event) {
    // Clear sessions
    session()->destroy();
    
    // Log activity
    ActivityLog::create([
        'user_id' => $event->userId,
        'action' => 'logout',
        'created_at' => date('Y-m-d H:i:s')
    ]);
});

// Usage
EventDispatcher::dispatch(new UserLoggedIn(123, '192.168.1.1'));

Order Processing Pipeline

class OrderPlaced extends Event {
    public function __construct(public int $orderId) {}
}

// Step 1: Validate payment
EventDispatcher::listen('OrderPlaced', function($event) {
    $order = Order::find($event->orderId);
    validatePayment($order);
});

// Step 2: Update inventory
EventDispatcher::listen('OrderPlaced', function($event) {
    $order = Order::find($event->orderId);
    foreach ($order->items as $item) {
        decrementStock($item->product_id, $item->quantity);
    }
});

// Step 3: Send confirmation
EventDispatcher::listen('OrderPlaced', function($event) {
    $order = Order::find($event->orderId);
    sendOrderConfirmation($order->customer_email, $order);
});

// Step 4: Notify admin
EventDispatcher::listen('OrderPlaced', function($event) {
    notifyAdmin("New order #{$event->orderId}");
});

Real-Time Notifications

class NotificationSent extends Event implements ShouldBroadcast {
    public function __construct(
        public int $userId,
        public string $title,
        public string $message
    ) {}
    
    public function broadcastOn(): array {
        return [new PrivateChannel("user.{$this->userId}")];
    }
    
    public function broadcastAs(): string {
        return 'notification';
    }
    
    public function broadcastWith(): array {
        return [
            'title' => $this->title,
            'message' => $this->message,
            'timestamp' => time()
        ];
    }
}

// Send notification
$event = new NotificationSent(123, 'New Message', 'You have a new message');
EventDispatcher::dispatch($event);

// Automatically broadcasts to WebSocket channel: private-user.123

Event Utilities

Check for Listeners

// Check if event has listeners
if (EventDispatcher::hasListeners('UserRegistered')) {
    echo "Event has listeners";
}

// Get listener count
$count = EventDispatcher::getListenerCount('UserRegistered');
echo "Number of listeners: {$count}";

Clear Listeners

// Clear specific event listeners
EventDispatcher::clearEvent('UserRegistered');

// Clear all listeners
EventDispatcher::clear();

Testing Events

Mock events in tests:
use Lyger\Events\FakeBroadcast;

// Record events instead of broadcasting
FakeBroadcast::record('MessageSent');

// Assertions
assert(FakeBroadcast::assertDispatched('MessageSent'));
assert(FakeBroadcast::assertNotDispatched('OrderPlaced'));

// Get recorded events
$events = FakeBroadcast::events();

// Reset
FakeBroadcast::reset();

Performance Tips

For expensive operations, dispatch jobs instead of blocking:
EventDispatcher::listen('UserRegistered', function($event) {
    // Don't do this (blocks the request)
    sendWelcomeEmail($event->email);
    
    // Do this instead (async)
    SendWelcomeEmailJob::dispatch($event->email);
});
Batch similar events to reduce overhead:
class EventBatcher {
    private static array $batch = [];
    
    public static function add(Event $event): void {
        self::$batch[] = $event;
    }
    
    public static function flush(): void {
        foreach (self::$batch as $event) {
            EventDispatcher::dispatch($event);
        }
        self::$batch = [];
    }
}

// Collect events
EventBatcher::add(new UserViewed(123));
EventBatcher::add(new UserViewed(124));

// Process in batch
EventBatcher::flush();

Next Steps

WebSockets

Real-time communication

Jobs & Queues

Background processing