quellabs/signal-hub

Type-safe signal/slot event system

Maintainers

Package info

github.com/quellabs/signal-hub

pkg:composer/quellabs/signal-hub

Statistics

Installs: 63

Dependents: 3

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.27 2026-03-07 09:03 UTC

This package is auto-updated.

Last update: 2026-03-07 09:06:35 UTC


README

Latest Version License Downloads

A Qt-inspired signal-slot implementation for PHP. Loose coupling between components through automatic signal discovery, with PHP's type system handling slot type safety.

Installation

composer require quellabs/signal-hub

Basic Usage

Declare signals as typed properties — your class needs no knowledge of the hub:

use Quellabs\SignalHub\Signal;

class MollieController {

    public Signal $paymentPaid;
    public Signal $paymentFailed;

    public function handleWebhook(array $data): void {
        $payment = $this->fetchPayment($data['id']);
        $payment->isPaid() ? $this->paymentPaid->emit($payment) : $this->paymentFailed->emit($payment);
    }
}

Connect directly if you hold a reference, or via the hub if you don't. The hub needs to know about the object first — call registerSignals() when instantiating it:

$hub->registerSignals($controller);

// Then connect directly...
$controller->paymentPaid->connect(fn(Payment $p) => ...);

// ...or via the hub if you don't hold a reference
$hub->getSignal(MollieController::class, 'paymentPaid')->connect(fn(Payment $p) => ...);

Standalone signals work without any owning object:

$signal = new Signal('app.booted');
$signal->connect(fn() => ...);
$signal->emit();

Framework Integration

Call registerSignals() from whatever instantiates your objects. The emitting class stays hub-unaware:

$hub->registerSignals($controller);

try {
    $controller->handle($request);
} finally {
    $hub->unregisterSignals($controller);
}

Consumers connect in their constructor — no controller reference needed:

class InventoryService {
    public function __construct(SignalHub $hub) {
        $hub->getSignal(OrderController::class, 'orderPlaced')
            ->connect($this->onOrderPlaced(...));
    }
}

Hub API

$hub->getSignal(MollieController::class, 'paymentPaid'); // by class name
$hub->getSignal('app.booted');                           // standalone signal
$hub->findSignals('payment.*');                          // wildcard search
$hub->findSignals('payment.*', $controller);             // wildcard + instance

Advanced Features

Priorities — control slot execution order:

$signal->connect($auditHandler, 100);   // runs first
$signal->connect($cleanupHandler, -10); // runs last

Meta-signals — react to hub activity:

$hub->signalRegistered()->connect(function(Signal $signal) {
    if (str_starts_with($signal->getName(), 'payment.')) {
        $signal->connect($this->auditLogger(...));
    }
});

Architecture

Three classes, no traits:

  • Signal — holds connections, emits to slots (connect, disconnect, emit)
  • SignalHub — registry and rendezvous point (registerSignals, unregisterSignals, getSignal, findSignals)
  • SignalHubLocator — optional static accessor for use outside DI contexts

Object-owned signals are stored in a WeakMap, so they're garbage collected when the owning object goes out of scope.

License

MIT