saloonphp/laravel-http-sender
Laravel HTTP Sender for Saloon routes Saloon API requests through Laravel's HTTP client, letting you use Laravel's config, middleware, and testing helpers. A lightweight bridge that keeps Saloon requests native to your Laravel app.
Install
composer require saloonphp/laravel-http-sender
This pulls in saloonphp/saloon (the framework-agnostic core) and registers the Laravel bridge via package auto-discovery — no manual providers entry in bootstrap/providers.php.
First five minutes
php artisan vendor:publish --provider="Saloon\Laravel\SaloonServiceProvider"
php artisan list | grep saloon) — they are the fastest way to scaffold a Connector/Request pair with the right base class and traits.php artisan saloon:connector AcmeApi
php artisan saloon:request AcmeApi GetCustomerRequest
public function __invoke(AcmeApi $acme)
{
return $acme->send(new GetCustomerRequest($id))->json();
}
First real use case: wrapping a single third-party API behind a Connector, then immediately writing a feature test with Saloon::fake(). That feedback loop — connector, request, fake, assert — is the whole package in miniature and covers 90% of day-to-day usage.
Where to look first: the published config/saloon.php (sender / default settings), the Saloon facade (faking, recording, assertions), and the base Connector/Request classes generated by the commands. Read those three things before reading any vendor source.
Upgrade note for v3.1.0: this release is additive — Laravel 12.x compatibility plus a PHP 8.4 implicit-nullable deprecation fix. No breaking changes, no config changes, no migration steps. If you are on Laravel 12 or PHP 8.4, pin ^3.1 rather than ^3.0.
One Connector per external service, many Requests per Connector. Put baseUrl(), authentication, default headers, retry behaviour, and timeouts on the Connector; keep each Request focused on a single endpoint (method, path, body, query). This makes mocking coarse-grained (whole service) or fine-grained (single request) without touching call sites.
Let the container build your requests. Type-hinting AcmeApi in a controller, job, or listener means the sender is resolved from the Laravel container, which is what makes Saloon::fake() and any custom sender binding take effect. Manual new AcmeApi() inside a service provider's register() or in a config/*.php file bypasses container timing and is the most common reason fakes "don't work".
Authentication lives on the connector, not in callers. Use the generated auth classes (saloon:auth) for token-based flows; the connector's defaultAuth() then applies uniformly and refreshes are handled once instead of at every call site.
Testing is the primary workflow, not an afterthought. In feature tests:
Saloon::fake([
GetCustomerRequest::class => MockResponse::make(['id' => 1, 'name' => 'Acme'], 200),
]);
// ... exercise the code under test ...
Saloon::assertSent(GetCustomerRequest::class);
Saloon::assertSent(function (GetCustomerRequest $request) {
return str_contains($request->getUrl(), '/customers/1');
});
Use MockResponse::sequence([...]) for pagination/retry paths and assert on assertSentCount when a loop is supposed to batch rather than hammer.
Record once, replay forever. During initial integration work, record real responses into fixtures (typically under tests/Fixtures/Saloon/) and commit them. Convert to hand-written MockResponses only where you need deliberate edge cases (429s, malformed bodies, empty pages). Recorded fixtures keep test suites honest about real payload shapes; written mocks keep them honest about failure handling. You usually want both.
Isolate network calls behind a job. For anything user-facing and slow, dispatch a queued job that sends the request and maps the response into Eloquent/domain state. Keep Saloon's own retry middleware for transient 5xx/connection blips, and let queue retries handle broader failures — don't let the two multiply out to an accidental request storm.
Map responses into DTOs at the boundary. Either use a response class per request or map in a small service. Returning raw ->json() arrays deep into your app is how third-party schema changes become app-wide bugs.
Saloon::fake() and Http::fake() are not the same interception layer. Saloon fakes at the sender level; code that bypasses Saloon and calls Http:: directly will not be caught by Saloon::assertSent(), and vice versa. If a request escapes your fake and hits the network in CI, look for a stray Http:: call or a manually constructed connector first.new Connector() inside a factory method called before the app is booted, or cached singletons built in a service provider, can silently avoid the fake. Bind or type-hint; don't new.Authorization values. Sanitise before committing, and never record against production credentials. This is the highest-severity routine mistake with this package.function handle(Foo $foo = null) will still emit deprecations. Write ?Foo $foo = null.composer update here doesn't guarantee the rest of the Saloon ecosystem is 12-ready.^3.1; earlier 3.x predates both fixes and will surface deprecation noise or install conflicts that look like your bug but aren't.How can I help you explore Laravel packages today?