Define Pulsar Contracts and Adapters
A Pulsar Contract is a consumer-owned Domain capability, and an Infrastructure Adapter is its concrete outbound implementation. Bind the Contract at the Laravel provider seam while keeping workflow behavior inward and vendor failures translated at the boundary.
Own a capability, not an implementation
The consuming Domain owns app/Pulsar/Domain/{Domain}/Contracts/{Capability}.php. Name it for the capability—PaymentGateway, not PaymentGatewayContract or PaymentGatewayInterface. A Contract is warranted for a genuine alternate, fake, or strategy; it must not wrap pure Domain behavior or become a junk drawer.
Its signatures may use passive DTOs, Enums, Value Objects, Models, Events, other Contracts, Laravel contracts, scalars, and void. It must not import Services or concrete Infrastructure, and it must not behaviorally invoke a UseCase, Action, or Operation.
Implement at the outbound boundary
An Adapter lives at app/Pulsar/Infrastructure/{Area}/{Adapter}.php, implements the Domain Contract, and may depend on vendor or framework code. It translates vendor or framework failures into the owning Domain Exception before they travel inward. Infrastructure must not import Service delivery or workflow types.
Context: checked binding fixture — `name(): string` proves resolution only, not a universal payment signature.
$this->app->bind(
PaymentGateway::class,
FakePaymentGateway::class,
);PaymentGateway, FakePaymentGateway, and contextual AdminPaymentGateway are fixture names. They establish ordinary, scoped, and contextual container resolution; they do not establish payment design, vendor translation, authorization, idempotency, or tenant isolation.
Choose a binding lifetime deliberately
| Selection | Provider seam | Use and non-claim |
|---|---|---|
| Regular | bind(Contract::class, Adapter::class) in register() | Fresh resolution for a stateless or unshared implementation; not every stateless Adapter must be a singleton. |
| Singleton | singleton() for deliberately shared stateless state | Never capture request, actor, tenant, mutable configuration, container, or retry state. |
| Scoped | scoped() for request/job-lifetime actor or tenant context | Laravel resets scope for Octane requests and queue jobs; it does not authorize or isolate a tenant. |
| Contextual | when(Concrete::class)->needs(Contract::class)->give(Adapter::class) | Selects an audience implementation; it does not move Domain ownership or replace retry policy. |
Retries reuse a stable idempotency key at the side-effect boundary. A binding or translation does not guarantee retry safety. Generated register() owns bindings; generated boot() owns policy guessing, Gates/hooks, and optional observers. Keep Laravel contracts distinct from Domain-owned Contracts.
Read Dependency direction for permitted calls, Octane for retained runtime state, and Anti-patterns for corrections.