ricardofiorani/guzzle-psr18-adapter
Tiny adapter that lets you use Guzzle as a PSR-18 HTTP client. Bridges PSR-7 requests/responses and promises to the PSR-18 ClientInterface, easing migration to standards-based HTTP without rewriting existing Guzzle integrations.
Installation:
composer require ricardofiorani/guzzle-psr18-adapter
Add to composer.json under require if not using Composer globally.
Basic Usage: Create a PSR-18 HTTP client instance:
use Ricardofiorani\GuzzlePsr18Adapter\GuzzleHttpClient;
use GuzzleHttp\Client as GuzzleClient;
$guzzleClient = new GuzzleClient();
$psr18Client = new GuzzleHttpClient($guzzleClient);
First Use Case: Send a simple GET request:
$response = $psr18Client->sendRequest(
new \Psr\Http\Message\Request('GET', 'https://api.example.com/data')
);
$body = $response->getBody()->getContents();
GuzzleHttpClient for implementation details.Adapter as a Wrapper:
Use the adapter to wrap an existing GuzzleHttp\Client instance, enabling PSR-18 compliance in legacy or mixed-codebase projects.
$guzzleClient = new GuzzleClient(['base_uri' => 'https://api.example.com']);
$psr18Client = new GuzzleHttpClient($guzzleClient);
Middleware Integration: Leverage Guzzle’s middleware stack while adhering to PSR-18:
$stack = \GuzzleHttp\HandlerStack::create();
$stack->push(\GuzzleHttp\Middleware::mapResponse(function ($response) {
return $response->withHeader('X-Custom', 'Value');
}));
$guzzleClient = new GuzzleClient(['handler' => $stack]);
$psr18Client = new GuzzleHttpClient($guzzleClient);
Dependency Injection:
Register the adapter in your DI container (e.g., Laravel’s AppServiceProvider):
$this->app->singleton(\Psr\Http\Client\ClientInterface::class, function ($app) {
return new GuzzleHttpClient(
new GuzzleClient($app['config']['http.client.options'])
);
});
Async Requests: Use Guzzle’s async capabilities via PSR-18 (though PSR-18 itself is synchronous):
$promise = $psr18Client->sendAsyncRequest(
new \Psr\Http\Message\Request('GET', 'https://api.example.com/data')
);
$response = $promise->wait(); // Blocking; consider using a library like `react/promise` for async.
Http facade for hybrid usage:
use Illuminate\Support\Facades\Http;
$response = Http::withOptions(['handler' => $guzzleClient->getHandler()])
->get('https://api.example.com/data');
$mockClient = $this->createMock(\Psr\Http\Client\ClientInterface::class);
$mockClient->method('sendRequest')->willReturn($mockResponse);
RequestException (Guzzle) or TransferException (PSR-18):
try {
$response = $psr18Client->sendRequest($request);
} catch (\Psr\Http\Client\ClientExceptionInterface $e) {
// Handle PSR-18 errors.
}
Async Limitations:
sendAsyncRequest is a thin wrapper around Guzzle’s async; avoid mixing with reactive libraries (e.g., ReactPHP) without explicit handling.wait() or then() sparingly; prefer synchronous calls where possible.Middleware Conflicts:
Request objects).mapRequest/mapResponse cautiously.Deprecated Guzzle Features:
^6.5 or ^7.0) in composer.json and monitor for updates.Response Validation:
\Psr\Http\Message\ResponseInterface, but Guzzle’s responses may lack expected methods (e.g., getStatusCode()).if (!$response instanceof \Psr\Http\Message\ResponseInterface) {
throw new \RuntimeException('Invalid PSR-18 response');
}
Log Requests/Responses: Add Guzzle’s logging middleware to debug:
$stack->push(\GuzzleHttp\Middleware::tap(function ($request) {
\Log::debug('Request:', ['url' => (string) $request->getUri()]);
}));
Check Headers:
Ensure headers are properly formatted (e.g., Content-Type):
$request = new \GuzzleHttp\Psr7\Request('GET', 'https://example.com');
$request = $request->withHeader('Content-Type', 'application/json');
Handler Stack Inspection: Dump the handler stack to debug middleware order:
\Log::debug('Handler stack:', [$guzzleClient->getConfig('handler')->getHandlers()]);
Custom Response Factories: Override the adapter’s response factory to transform Guzzle responses:
class CustomGuzzleHttpClient extends GuzzleHttpClient {
public function sendRequest(\Psr\Http\Message\RequestInterface $request) {
$response = parent::sendRequest($request);
return $this->customizeResponse($response);
}
protected function customizeResponse($response) {
return new \Psr\Http\Message\Response(
$response->getStatusCode(),
$response->getHeaders(),
$response->getBody()
);
}
}
Event Dispatching: Integrate with Laravel events or other frameworks:
$stack->push(\GuzzleHttp\Middleware::tap(function ($request) use ($dispatcher) {
$dispatcher->dispatch('http.request', [$request]);
}));
Retry Logic: Use Guzzle’s retry middleware via PSR-18:
$stack->push(\GuzzleHttp\Middleware::retry(
function ($retries, \GuzzleHttp\Exception\RequestException $exception) {
return $retries < 3 && $exception->hasResponse();
},
function (\GuzzleHttp\Exception\RequestException $exception) {
return 500;
}
));
How can I help you explore Laravel packages today?