jord-jd/psr-18-guzzle-adapter
Simple PSR-18 adapter for the Guzzle HTTP client, letting you use Guzzle wherever a PSR-18 (ClientInterface) implementation is required. Lightweight, focused package for bridging PSR-compliant libraries with Guzzle.
Install the Package
composer require jord-jd/psr-18-guzzle-adapter
Bind the Adapter in Laravel
In AppServiceProvider@register():
use JordJD\Psr18GuzzleAdapter\GuzzleAdapter;
use GuzzleHttp\Client;
$this->app->singleton(\Psr\Http\Client\ClientInterface::class, function ($app) {
return new GuzzleAdapter(new Client([
'base_uri' => 'https://api.example.com',
]));
});
First PSR-18 Request
use Psr\Http\Message\RequestInterface;
use Psr\Http\Client\ClientInterface;
public function __construct(private ClientInterface $client) {}
public function fetchData()
{
$request = new \GuzzleHttp\Psr7\Request('GET', '/data');
$response = $this->client->sendRequest($request);
return json_decode($response->getBody(), true);
}
GuzzleAdapter.php – Understand how Guzzle methods map to PSR-18.ClientInterface – Reference for available methods.Illuminate\Support\Facades\Http) for consistency.Service Provider Binding
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->bind(\Psr\Http\Client\ClientInterface::class, function ($app) {
return new GuzzleAdapter(
new \GuzzleHttp\Client($app['config']['http.client'])
);
});
}
Constructor Injection
use Psr\Http\Client\ClientInterface;
class ApiService
{
public function __construct(private ClientInterface $client) {}
public function getUser(int $id)
{
$request = new \GuzzleHttp\Psr7\Request('GET', "/users/{$id}");
$response = $this->client->sendRequest($request);
return json_decode($response->getBody(), true);
}
}
Using PSR-7 Requests
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;
public function createRequest(): RequestInterface
{
return new Request('POST', '/endpoint', [
'Content-Type' => 'application/json',
], json_encode(['key' => 'value']));
}
Reusing Guzzle’s Request Factory
use GuzzleHttp\Psr7\Request;
public function __construct(private GuzzleAdapter $adapter) {}
public function send()
{
$request = $this->adapter->getAdapter()->createRequest('GET', '/data');
return $this->adapter->sendRequest($request);
}
Streaming Responses
public function streamResponse(ClientInterface $client)
{
$request = new Request('GET', '/large-file');
$response = $client->sendRequest($request);
while (!$response->getBody()->eof()) {
echo $response->getBody()->read(1024);
}
}
Error Handling
use Psr\Http\Client\ClientExceptionInterface;
public function safeRequest(ClientInterface $client)
{
try {
$response = $client->sendRequest(new Request('GET', '/data'));
return json_decode($response->getBody(), true);
} catch (ClientExceptionInterface $e) {
// Handle Guzzle-specific exceptions (wrapped in PSR-18)
return ['error' => $e->getMessage()];
}
}
PSR-15 Middleware Stack
use League\Pipeline\Pipeline;
use Psr\Http\Client\Middleware;
$pipeline = new Pipeline([
new class implements Middleware {
public function __invoke(callable $handler, RequestInterface $request) {
// Add auth header
$request = $request->withHeader('Authorization', 'Bearer token');
return $handler($request);
}
},
]);
$client = new GuzzleAdapter(
new \GuzzleHttp\Client(),
$pipeline
);
Guzzle Middleware (Limited Support)
// Note: Not all Guzzle middleware is PSR-18 compatible
$client = new GuzzleAdapter(
new \GuzzleHttp\Client([
'middleware' => [
new \GuzzleHttp\Middleware::tap(function ($request) {
// Pre-request logic
}),
],
])
);
Mocking PSR-18 Client
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
$mockClient = \Mockery::mock(ClientInterface::class);
$mockClient->shouldReceive('sendRequest')
->withArgs(function (RequestInterface $request) {
return $request->getUri()->getPath() === '/data';
})
->andReturn(new \GuzzleHttp\Psr7\Response(200, [], '{"id": 1}'));
$this->app->instance(ClientInterface::class, $mockClient);
Pest Test Example
it('fetches data via PSR-18 client', function () {
$client = \Mockery::mock(ClientInterface::class);
$client->shouldReceive('sendRequest')
->once()
->andReturn(new \GuzzleHttp\Psr7\Response(200, [], '{"test": true}'));
$this->app->instance(ClientInterface::class, $client);
$service = new ApiService($this->app->make(ClientInterface::class));
expect($service->fetchData())->toBe(['test' => true]);
});
Guzzle-Specific Features Unavailable
TransferStats are not exposed via PSR-18.$guzzleClient = $adapter->getAdapter();
$guzzleClient->getEmitter()->getEvents();
Middleware Mismatch
Response Body Consumption
$response->getBody()->getContents() or iterate the stream:
$body = $response->getBody();
while (!$body->eof()) {
echo $body->read(1024);
}
Type Hinting Conflicts
RequestInterface not implementing PSR-7 methods (e.g., withHeader).GuzzleHttp\Psr7\Request for type safety:
$request = new \GuzzleHttp\Psr7\Request('GET', '/data');
Async Requests
$promise = $adapter->getAdapter()->sendAsync($request);
Log Requests/Responses
$request = new Request('GET', '/data');
$response = $client->sendRequest($request);
\Log::debug('Request:', [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'headers' => $request->getHeaders(),
]);
Inspect Guzzle Internals
$guzzleClient = $adapter->getAdapter();
$guzzleClient->getConfig('debug'); // Enable debug mode
Handle Exceptions Gracefully
try {
$response = $client->sendRequest($request);
} catch (\Psr\Http\Client\ClientExceptionInterface $e) {
\Log::error('HTTP Client Error:', [
'message' => $e->getMessage(),
'request' => $request,
]);
throw new \RuntimeException('API request failed', 0, $e);
}
base_uri config. Set it manually:
$request = new Request('GET', '/data');
How can I help you explore Laravel packages today?