frodeborli/gjallarhorn

Event emitter functionality with on(), off(), once() for subscribing to events.

1.0.2 2022-12-03 23:49 UTC

This package is auto-updated.

Last update: 2024-12-04 04:10:14 UTC


README

Subscribe to, and emit events. Supports both asynchronous events using PHP 8 Fibers and synchronous events.

Usage

Add the EventEmitter interface and use the EventEmitterTrait to any class that emits events.

Event emitter class

use Gjallarhorn\{
    EventEmitter,
    EventEmitterTrait
};

/**
 * These annotations allow you to provide better type hinting in your IDE. Change the `Event $event` according
 * to your requirements. In this case we're using PHP 8.1 enums (see below for more information).
 * 
 * @method void on(MatchEvent $event, Closure ...$listener) Subscribe to an event
 * @method void off(MatchEvent $event, Closure ...$listener) Unsubscribe from an event
 * @method void once(MatchEvent $event, Closure ...$listener) Subscribe to only the next invocation of this event
 */
class FootballMatch implements EventEmitter {
    use EventEmitterTrait;

    /**
     * This function sends off a synchronous event message. If this
     * event is emitted from inside an asynchronous event listener,
     * then this event will effectively also be asynchronous.
     */
    public function triggerGoalEvent(int $player_number, bool $homeTeam) {
        return $this->trigger(MatchEvent::GOAL, $player_number, $homeTeam);
    }

    /**
     * This function emits the event and returns a Promise object which
     * can be resolved later by calling `$promise->wait()`.
     */
    public function triggerGoalEventAsync(int $player_number, bool $homeTeam) {
        return $thsi->triggerAsync(MatchEvent::GOAL, $player_number, $homeTeam);
    }
}

Subscribing and unsubscribing to events

Adding listeners to an event source object is easy:

/**
 * An event handler
 */
function handle_goal_scores(...$args) {
    // handle the event
}

/**
 * Subscribing to goal scores
 */
$match->on(SomeType::GOAL_SCORED, handle_goal_scores(...));

/**
 * Unsubscribing
 */
$match->off(SomeType::GOAL_SCORED, handle_goal_scores(...));

/**
 * Subscribe only to the next goal
 */
$match->once(SomeType::GOAL_SCORED, function(int $id, DateTimeInterface $time) {
    // The next event
});

Asynchronous event handlers

Events can be triggered asynchronously. In that case the EventEmitterTrait::trigger() function will return a Promiselike object (which is any object having a method with a signature compatible with function then($onFulfilled, $onRejected).

TIP To handle various promise flavors you may consider using the RaceTrack\Promise::promisify() method. This method will return a RaceTrack\Promise instance with all the functionality you need to work with asynchronous code.

Running asynchronous event listeners

<?php
use Gjallarhorn\{
    EventEmitter,
    EventEmitterTrait
};

enum MyEvent: string {
    case GOOD_EVENT = 'GOOD_EVENT';
    case BAD_EVENT = 'BAD_EVENT';
}

/**
 * @method void on(MyEvent $event, Closure ...$listener) Subscribe to an event
 * @method void off(MyEvent $event, Closure ...$listener) Unsubscribe from an event
 * @method void once(MyEvent $event, Closure ...$listener) Subscribe only to the next invocation of the event
 */
class SomeClass implements EventEmitter {
    use EventEmitterTrait;

    public function triggerGoodEvent(): void {
        $promise = $this->triggerAsync(MyEvent::GOOD_EVENT, $this);
        echo "This happens before all events have been resolved\n";
        $results = $promise->await();
    }
}

$instance = new SomeClass();

$instance->on(MyEvent::GOOD_EVENT, function(SomeClass $emitter) {
    // Sleep for 2 seconds while allowing other code to progress
    Fiber::suspend(Promise::sleep(2));
    echo "I've slept for 2 seconds\n";
});

$instance->triggerGoodEvent();