saloonphp/saloon
Saloon is a PHP HTTP client framework for building clean, reusable API integrations. Create connectors and requests, handle auth, middleware and retries, mock and test easily, and keep endpoints organized with strong typing and a fluent DX.
Installation:
composer require saloonphp/saloon
For Laravel, add to config/app.php under providers:
Saloon\Laravel\SaloonServiceProvider::class,
First Request:
Define a request class (e.g., GetUserRequest.php):
namespace App\Saloon\Requests;
use Saloon\Contracts\Request;
use Saloon\Enums\HttpMethod;
use Saloon\Traits\BodyTrait;
use Saloon\Traits\HeadersTrait;
class GetUserRequest implements Request
{
use BodyTrait, HeadersTrait;
public function method(): string
{
return HttpMethod::GET;
}
public function endpoint(): string
{
return '/users/1';
}
}
Send the Request:
use App\Saloon\Requests\GetUserRequest;
use Saloon\Http\Connector;
$connector = new Connector();
$response = $connector->send(new GetUserRequest());
$user = $response->json();
First Connector:
Extend Connector for shared config (e.g., StripeConnector.php):
namespace App\Saloon\Connectors;
use Saloon\Http\Connector;
use Saloon\Traits\BaseUrlTrait;
class StripeConnector extends Connector
{
use BaseUrlTrait;
protected string $baseUrl = 'https://api.stripe.com/v1';
protected string $defaultAuth = 'BearerAuth'; // Auto-injects auth
}
Define Authenticator (e.g., OAuth2Authenticator.php):
use Saloon\Contracts\Authenticator;
use Saloon\Traits\Auth\OAuth2TokenTrait;
class OAuth2Authenticator implements Authenticator
{
use OAuth2TokenTrait;
public function authenticate(): array
{
return [
'Authorization' => 'Bearer ' . $this->accessToken,
];
}
}
Attach to Connector:
class StripeConnector extends Connector
{
protected string $defaultAuth = OAuth2Authenticator::class;
// ...
}
Send Authenticated Request:
$connector->send(new GetUserRequest());
examples directory in the repo for real-world use cases (e.g., Stripe, GitHub, OAuth2).SaloonServiceProvider for Laravel-specific features like binding connectors to the container.Pattern: Group related API endpoints under a single Connector class to centralize:
Example:
class StripeConnector extends Connector
{
use BaseUrlTrait, HeadersTrait;
protected string $baseUrl = 'https://api.stripe.com/v1';
protected string $defaultAuth = 'BearerAuth';
public function resolveBaseUrl(): string
{
return config('services.stripe.url');
}
public function defaultHeaders(): array
{
return [
'Idempotency-Key' => $this->generateIdempotencyKey(),
];
}
}
Workflow:
StripeConnector, TwilioConnector).$stripe = app(StripeConnector::class);
$response = $stripe->send(new CreateCustomerRequest());
Pattern: Use request classes to override connector defaults per endpoint. Key Methods:
endpoint(): Override the URL.method(): Change HTTP method (GET, POST, etc.).headers()/body(): Add request-specific data.auth(): Use a different authenticator.Example:
class CreateChargeRequest extends Request
{
use BodyTrait;
public function method(): string
{
return HttpMethod::POST;
}
public function endpoint(): string
{
return '/charges';
}
public function body(): array
{
return [
'amount' => $this->amount,
'currency' => 'usd',
'source' => $this->paymentMethodId,
];
}
public function auth(): string
{
return 'BearerAuth'; // Overrides connector default
}
}
Pattern: Use middleware to handle retries, logging, or transformations. Common Middleware:
RetryMiddleware: Automatic retries for transient failures.LoggingMiddleware: Log requests/responses.TransformMiddleware: Convert responses to DTOs.Example:
use Saloon\Contracts\Middleware;
use Saloon\Traits\Middleware\TransformsResponseTrait;
class TransformUserResponseMiddleware implements Middleware
{
use TransformsResponseTrait;
public function handle($request, callable $next)
{
$response = $next($request);
return $this->transformResponse($response, UserDto::class);
}
}
Attach to Connector:
class StripeConnector extends Connector
{
protected array $middleware = [
RetryMiddleware::class,
LoggingMiddleware::class,
TransformUserResponseMiddleware::class,
];
}
Pattern: Centralize auth logic in authenticators. Supported Auth Types:
BearerAuth)OAuth2Authenticator)BasicAuth)ApiKeyAuth)Example: OAuth2 Flow:
class OAuth2Authenticator implements Authenticator
{
use OAuth2TokenTrait;
public function authenticate(): array
{
$this->refreshTokenIfNeeded();
return ['Authorization' => 'Bearer ' . $this->accessToken];
}
protected function refreshTokenIfNeeded(): void
{
if ($this->isTokenExpired()) {
$this->refreshAccessToken();
}
}
}
Attach to Connector:
class GitHubConnector extends Connector
{
protected string $defaultAuth = OAuth2Authenticator::class;
}
Pattern: Use MockClient to simulate API responses in tests.
Workflow:
tests/Fixtures).Example:
use Saloon\Testing\MockClient;
beforeEach(function () {
MockClient::shouldReceive('send')
->once()
->with(GetUserRequest::class)
->andReturnFromFixture('users/user_1.json');
});
it('fetches a user', function () {
$user = $this->stripe->send(new GetUserRequest())->json();
expect($user['id'])->toBe('user_123');
});
Fixture File (tests/Fixtures/users/user_1.json):
{
"id": "user_123",
"name": "John Doe"
}
Pattern: Use exceptions and middleware to handle errors gracefully. Key Classes:
SaloonException: Base exception.ConnectionException: Network failures.HttpException: HTTP errors (4xx/5xx).Example:
use Saloon\Exceptions\HttpException;
try {
$response = $connector->send(new GetUserRequest());
} catch (HttpException $e) {
if ($e->response->status == 404) {
// Handle not found
}
throw $e;
}
Custom Error Handling Middleware:
class HandleStripeErrorsMiddleware implements Middleware
{
public function handle($request, callable $next)
{
try {
return $next($request);
} catch (HttpException $e) {
if ($e->response->status == 400) {
throw new \RuntimeException('Invalid Stripe request: ' . $e->response->json()['error']['message']);
}
throw $e;
}
}
}
Pattern: Build requests dynamically using RequestBuilder.
Use Case: Construct requests with runtime data (
How can I help you explore Laravel packages today?