Signal Wiring
Canvas automatically discovers signals on controllers and aspects 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.
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');
}
}
Declaring Signals on an Aspect
Aspects can declare signals the same way controllers do. Canvas discovers signals on all aspects applied to the current controller method. This allows reusable cross-cutting concerns — such as rate limiting, authentication, or tracking — to communicate with the rest of the application without coupling to any specific controller.
Creating Signal Listeners
A signal listener wires listener methods to signals using @ListenTo annotations. Canvas recursively discovers
listener classes inside /src/Listeners, though the discovery location can be changed in
app.php. Listener constructors support dependency injection through autowiring.
Alternatively, @ListenTo annotations can be placed directly on methods of the current controller,
allowing controllers to respond to signals without a dedicated listener class.
Values passed to emit() are forwarded to listener method parameters in the same order they are provided.
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 request lifetime. They are available only during the current request. Canvas discovers and initializes signals on both the controller and all active aspects before execution, and cleans them up automatically afterward — including when exceptions occur. No manual cleanup is needed in application code.