Signal Wiring

Canvas automatically discovers signals on controllers and wires them to listener methods declared with @ListenTo annotations. A Signal is an object that emits data to interested listeners identified by a shared signal name. No manual registration is required in application code. Signals are inspired by the Qt signal/slot model. For the underlying Signal/Slot API — connecting slots manually, priorities, and the SignalHub registry — see SignalHub.

explanation

Declaring Signals on a Controller

Declare public Signal properties and give each an explicit name. The name is the contract that listeners use to identify which signals they handle:

use Quellabs\SignalHub\Signal;

class MollieController extends BaseController {

    public Signal $paymentPaid;
    public Signal $paymentFailed;

    public function __construct() {
        $this->paymentPaid   = new Signal('mollie.payment.paid');
        $this->paymentFailed = new Signal('mollie.payment.failed');
    }

    /**
     * @Route("/webhook/mollie")
     */
    public function webhook(Request $request): Response {
        $payment = $this->mollie->payments->get($request->get('id'));

        if ($payment->isPaid()) {
            $this->paymentPaid->emit($payment, 'hello');
        } else {
            $this->paymentFailed->emit($payment);
        }

        return new Response('OK');
    }
}

Creating a Signal Listener

A signal listener wires slots (listener methods) to signals using @ListenTo annotations. Canvas recursively discovers listener classes inside /src/Listeners. The discovery location may be changed by modifying app.php. Listener constructors support dependency injection through autowiring.

Values passed to emit() are forwarded to listener method parameters using the same argument order. Multiple arguments are supported and parameter types are not enforced by the signal system.

namespace App\Listeners;

use Quellabs\Canvas\Annotations\ListenTo;

class PaymentListener {

    /**
     * @ListenTo("mollie.payment.paid")
     */
    public function onPaymentPaid(mixed $payment, string $message): void {
        $this->logger->onPaymentPaid($payment, $message);
    }

    /**
     * @ListenTo("mollie.payment.failed")
     */
    public function onPaymentFailed(mixed $payment): void {
        $this->invoiceService->onPaymentFailed($payment);
    }
}

Signal Lifecycle

Signals are scoped to the controller's lifetime. They are available only during the current request. Canvas initializes them before controller execution and cleans them up automatically afterward, including when exceptions occur. No manual cleanup is needed in application code.