Listener
Listener reacts to a Domain Event in synchronous or queued delivery mode.
Generated facts
- Layer
- domain
- Generated path
app/Pulsar/Domain/{Domain}/Listeners/{Name}.php- Generator command
make:listener- Workflow method
- None
- Stability
- Current manifest surface (Pulsar 0.4.1)
Canonical example
Generated src/stubs/listener-queued.stub — canonical synchronized stub.
<?php
namespace {{namespace}};
{{eventImport}}
use Illuminate\Support\Facades\Gate;
class {{name}}
{
public function handle({{event}} $event): void
{
// Authorize this non-HTTP entrypoint when the reaction is actor-sensitive.
// Replace this framework string with the owning domain's backed Ability enum value.
Gate::authorize('replace-with-domain-ability', $event);
// Synchronous listeners may perform side effects or read-only invariant checks.
// Never call a UseCase, start a transaction, or assume this reaction is durable.
}
}
Responsibility
Listener owns the responsibility stated above; it must not absorb delivery, transaction, or unrelated cross-layer behavior.
Placement and dependencies
Keep this type in its generated Domain path. Domain is independent of delivery concerns; Infrastructure implements Domain-owned Contracts at its boundary.
Workflow and tests
Listener participates in delivery after Domain work; queued effects must remain idempotent and do not gain exactly-once delivery. Test its direct contract, its rejected boundary cases, and the caller or callee that proves the rule in application flow.
Three pitfalls
- ❌ Prohibited: move this responsibility into a neighboring type merely because it is nearby.
- ✅ Correct: keep the generated placement and depend only on the documented layer direction.
- ✅ Correct: test the boundary through the caller and the return or side effect visible to its callee.
Related reading
Read architecture placement for shared rationale and follow this page’s related concept links for the adjacent responsibility.
Boundaries
❌ Prohibited: Do not treat queued delivery as a guaranteed single delivery or put a transaction in a Listener.
✅ Correct: Make queued effects idempotent and let a called UseCase own transaction work.