Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Passage Laravel Package

morcen/passage

Morcen Passage is a lightweight Laravel package that provides a clean, expressive API for defining and managing state transitions and workflows on your Eloquent models. Keep complex business logic organized, track changes with ease, and integrate seamlessly into any Laravel application.

View on GitHub
Deep Wiki
Context7

Getting Started

Passage is a Laravel package for wrapping outbound HTTP integrations in first-class "handlers" (controller-like classes) with built-in retries, caching, signed requests, and event logging. Day-to-day, think of it as the boundary layer between your app and every third-party API you talk to.

Minimal setup:

  1. Install: composer require morcen/passage (v3.1.1 adds Laravel 12 support, so a fresh L12 app is fine).
  2. Publish the config: php artisan vendor:publish --tag=passage-config and set your upstream definitions (base_uri, allowed client headers, retry/cache defaults).
  3. Run php artisan passage:list before writing any code. It enumerates every registered handler your app knows about — this is your map of the integration surface and the fastest way to orient in an unfamiliar codebase.
  4. Scaffold your first handler: php artisan passage:controller ExternalApiController (or --with-retry if the upstream is flaky).
  5. Call it from a service or controller and return the response.

First use case: replacing a raw Http::get(...) call scattered in a controller with a single handler class that centralizes the base URI, auth headers, retry policy, and caching — then swapping that call site for a handler invocation.

Implementation Patterns

One handler per upstream service. Treat each handler as the owning class for one external API. It holds the base URI, default headers, and the retry/cache policy for that upstream. Everything else in the app should go through it rather than constructing HTTP calls directly.

Implement the interface, inject the abstraction. PassageControllerInterface (usage example added in this release) is the contract to type-hint against in your services. This keeps your app code testable and lets you swap a handler implementation without touching call sites. Prefer constructor injection over resolving handlers from the container by string.

Conditional retries with $when. The common production pattern is to retry only on transient failures — connection errors, 429, 5xx — and never on a 4xx that means your payload was wrong. Use the $when callback to encode that policy explicitly rather than accepting the default. Documented in this release, with a corrected @param signature, so check the docblock if you're on an older pin.

Scaffold, then harden. passage:controller --with-retry generates a handler already wired for retries. Treat the output as a starting point: review which verbs get retried, whether the backoff matches the upstream's rate-limit semantics, and whether idempotency keys are needed.

Signed upstreams (HMAC). The new HMAC controller stub gives you a starting shape for APIs that require request signing. Use it as a template for the signing header construction, then keep timestamps and secret retrieval in your own code — don't hardcode secrets into generated stubs.

Cache the reads, not the writes. PassageCacheManager is the right seam for memoizing GET responses that don't change often (reference data, config lookups). Key by handler + path + query so two handlers hitting the same URL don't collide. Never route a mutating request through the cache.

Test at the handler boundary. The release's test additions point at the patterns worth copying: assert on mixed-case client headers (HTTP headers are case-insensitive, and the package normalizes them), assert the response when a base URI is missing/invalid, and assert the retry callback fires the expected number of times for each failure class.

Observability via events. PassageEventSubscriber emits structured log lines per request/retry. In staging, tail these logs when a handler misbehaves — it's usually faster than adding dd() to the handler. If you already have a central log pipeline, subscribe to the same events and forward to your APM rather than duplicating logging in handlers.

Gotchas and Tips

  • Invalid base URIs now return structured JSON rather than throwing. As of v3.1.1 this surfaces as a response, not an exception. If your error handling relies on catching an exception for misconfiguration, it will silently stop firing — assert on the response shape in a test and fail loudly in dev.
  • passage:list is now resilient to broken handlers. That's a feature and a trap: a handler class that fails to resolve is skipped instead of blowing up the command. After adding a handler, re-run passage:list and confirm it's actually there — a typo in the namespace will now be quiet.
  • Header casing. HTTP headers are case-insensitive, but comparisons often aren't. The package normalizes allowed client headers (covered by new tests), but your own assertions and middleware may not. Always compare lowercased.
  • Retry + non-idempotent verbs. Scaffolded retry handlers will happily retry a POST. Make sure $when excludes anything that isn't safe to replay, or add an idempotency key.
  • Retry + cache interaction. Cache only after a successful attempt. If you cache inside the retry loop you'll pin a transient failure for the full TTL.
  • HMAC clock skew. Signed requests fail in confusing ways when the container clock drifts. If auth breaks only in CI or on one host, check time sync before reading the signing code.
  • Config quirks. base_uri joins are a classic source of doubled or missing slashes when the config value and the handler path both contribute a /. Keep one convention: no trailing slash in config.
  • Laravel 12 support landed in this release. If you were pinned to an older constraint because of the audit advisories, this is the release to bump. Run composer update and re-run your handler tests — the retry $when signature docblock was also corrected here, which is a hint people were getting the callback arity wrong.
  • Extension points worth knowing: the handler interface (for fakes in tests), the event subscriber (for custom metrics), and the cache manager (for swapping in a tagged or Redis-backed store). If you find yourself fighting the package, check whether one of those three is the intended override point before forking a handler.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo