Trigger.php

PHP

Path: src/Hooks/Trigger.php

<?php
namespace mini\Hooks;

use Closure;
use LogicException;

/**
 * Event that can only trigger ONCE
 * Late subscribers are called immediately with the original trigger data
 *
 * @template TPayload The primary payload type passed to listeners
 * @package mini\Hooks
 */
class Trigger extends Dispatcher {

    protected bool $triggered = false;

    /** @var list<mixed> */
    protected array $data = [];

    /** @var list<callable(TPayload, mixed...): void> */
    protected array $listeners = [];

    /**
     * Has this trigger already fired?
     *
     * @return bool
     */
    public function wasTriggered(): bool {
        return $this->triggered;
    }

    /**
     * Activate the trigger and run all listeners
     *
     * @param TPayload $payload
     * @param mixed ...$args Additional arguments
     * @throws LogicException If already triggered
     */
    public function trigger(mixed ...$args): void {
        if ($this->triggered) {
            throw new LogicException("Trigger '{$this->getDescription()}' already fired");
        }
        $this->triggered = true;
        $this->data = $args;
        $listeners = $this->listeners;
        $this->listeners = [];
        $this->invokeAll($listeners, ...$args);
    }

    /**
     * Subscribe to this trigger
     * If already triggered, listener is called immediately
     *
     * @param callable(TPayload, mixed...): void ...$listeners
     */
    public function listen(Closure ...$listeners): void {
        if ($this->triggered) {
            $this->invokeAll($listeners, ...$this->data);
        } else {
            foreach ($listeners as $listener) {
                $this->listeners[] = $listener;
            }
        }
    }

    /**
     * Unsubscribe from this trigger
     *
     * @param callable(TPayload, mixed...): void ...$listeners
     */
    public function off(Closure ...$listeners): void {
        self::filterArrays($listeners, $this->listeners);
    }
}