alextartan/guzzle-psr18-adapter
PSR-18 HTTP client adapter for Guzzle. Wraps Guzzle to provide a PSR-18 ClientInterface, letting you send PSR-7 requests and receive PSR-7 responses with standard client/network/request exceptions. Requires PHP 7.2+.
Installation:
composer require alextartan/guzzle-psr18-adapter
Ensure your composer.json includes "php": "^7.2" and "guzzlehttp/guzzle": "^6.0".
First Implementation:
Replace a Guzzle Client instance with the PSR-18 adapter in a service or controller:
use Alextartan\GuzzlePsr18Adapter\Psr18Adapter;
use GuzzleHttp\Client;
use Psr\Http\Message\RequestInterface;
$guzzleClient = new Client();
$psr18Client = new Psr18Adapter($guzzleClient);
$request = new \GuzzleHttp\Psr7\Request('GET', 'https://api.example.com/data');
$response = $psr18Client->sendRequest($request);
Type-Hinting:
Update method signatures to accept Psr\Http\Client\ClientInterface:
public function fetchData(ClientInterface $client) {
$request = new \GuzzleHttp\Psr7\Request('GET', 'https://api.example.com/data');
$response = $client->sendRequest($request);
// ...
}
Laravel Service Provider:
Bind the adapter in AppServiceProvider:
public function register() {
$this->app->bind(\Psr\Http\Client\ClientInterface::class, function ($app) {
return new Psr18Adapter(new Client([
'base_uri' => config('services.api.base_uri'),
]));
});
}
PSR-18 Compliance in Controllers:
Inject ClientInterface and use the adapter transparently:
public function getExternalData(ClientInterface $client) {
$request = new \GuzzleHttp\Psr7\Request('GET', '/data');
$response = $client->sendRequest($request);
return json_decode($response->getBody(), true);
}
Middleware Integration: Use Guzzle middleware via the underlying client:
$client = new Client([
'handler' => HandlerStack::create(new Middleware()),
]);
$psr18Client = new Psr18Adapter($client);
Request Factories: Reuse Guzzle’s request factories if needed:
$request = $psr18Client->getClient()->createRequest('POST', '/data', [
'json' => ['key' => 'value']
]);
Error Handling: Catch PSR-18 exceptions:
try {
$response = $client->sendRequest($request);
} catch (\Psr\Http\Client\ClientExceptionInterface $e) {
// Handle client errors (e.g., 4xx, 5xx)
} catch (\Psr\Http\Client\NetworkExceptionInterface $e) {
// Handle network issues
}
API Resource Wrappers: Create a facade or helper to abstract PSR-18 calls:
namespace App\Services;
use Psr\Http\Client\ClientInterface;
class ApiClient {
public function __construct(private ClientInterface $client) {}
public function getUserData(int $id) {
$request = new \GuzzleHttp\Psr7\Request('GET', "/users/{$id}");
return $this->client->sendRequest($request);
}
}
Dynamic Configuration: Use Laravel’s config to manage Guzzle settings:
$psr18Client = new Psr18Adapter(new Client(config('guzzle')));
Testing:
Mock ClientInterface in tests:
$mockClient = $this->createMock(ClientInterface::class);
$mockClient->method('sendRequest')->willReturn(new \GuzzleHttp\Psr7\Response(200));
$this->app->instance(ClientInterface::class, $mockClient);
Method Signature Mismatches:
request() or get() directly on the adapter (these are Guzzle-specific).sendRequest() and construct requests manually:
// ❌ Avoid
$response = $psr18Client->request('GET', '/data');
// ✅ Do
$request = new \GuzzleHttp\Psr7\Request('GET', '/data');
$response = $psr18Client->sendRequest($request);
Response Handling:
ResponseInterface lacks Guzzle’s convenience methods (e.g., json()).Response directly:
$body = $response->getBody()->getContents();
$data = json_decode($body, true);
Middleware Conflicts:
Async Limitations:
sendAsync).Client for async operations:
$promise = $psr18Client->getClient()->sendAsync($request);
Guzzle Version Lock:
Log Requests/Responses:
Add Guzzle’s tap middleware to the underlying client:
$client = new Client([
'handler' => HandlerStack::create([
new Middleware(),
new \GuzzleHttp\Middleware::tap(function ($request, $options) {
\Log::debug('Request:', [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'headers' => $request->getHeaders(),
]);
}),
]),
]);
Verify PSR-18 Compliance:
Use php-http/client-tests to validate compliance:
composer require --dev php-http/client-tests
vendor/bin/client-tests --client=Alextartan\GuzzlePsr18Adapter\Psr18Adapter
Check Underlying Client:
Access the Guzzle Client for advanced debugging:
$guzzleClient = $psr18Client->getClient();
$config = $guzzleClient->getConfig();
Custom Request Factories: Extend the adapter to support custom request creation:
class CustomPsr18Adapter extends Psr18Adapter {
public function createRequest(string $method, string $uri, array $options = []) {
return $this->getClient()->createRequest($method, $uri, $options);
}
}
Response Decorators: Wrap responses to add Guzzle-like convenience methods:
$response = $psr18Client->sendRequest($request);
$data = json_decode($response->getBody(), true);
Fork and Maintain: Fork the repository to add support for Guzzle 7/8 or PHP 8.2+ if needed. Key changes might include:
composer.json dependencies.Integration with Laravel HTTP Client: Use the adapter with Laravel’s HTTP client facade for consistency:
use Illuminate\Support\Facades\Http;
Http::macro('withPsr18Client', function () {
return new Psr18Adapter(new Client());
});
Base URI Handling:
Configure the underlying Guzzle Client with a base URI:
$psr18Client = new Psr18Adapter(new Client([
'base_uri' => 'https://api.example.com/v1/',
]));
Timeouts: Set timeouts via Guzzle’s config:
$psr18Client = new Psr18Adapter(new Client([
'timeout' => 10.0,
'connect_timeout' => 5.0,
]));
Authentication: Use Guzzle’s auth middleware:
$client = new Client([
'auth' => ['user', 'pass'],
]);
$psr18Client = new Psr18Adapter($client);
How can I help you explore Laravel packages today?