php-http/guzzle5-adapter
PHP-HTTP adapter that lets you use Guzzle 5 as the underlying HTTP client. It implements the PHP-HTTP client interfaces for sending requests and handling responses, making it useful for legacy projects already built on Guzzle 5.
This is a legacy bridge, not a modern client. Use it only when the application is already pinned to Guzzle 5 and a third-party library requires php-http/client-implementation or type-hints Http\Client\HttpClient. The package is archived and Guzzle 5 is end-of-life, so do not introduce it into a new Laravel application.
First, verify the constraint:
composer show guzzlehttp/guzzle
# Must report 5.x. If it reports 6.x or 7.x, stop here.
Then install the adapter and a PSR-7 message implementation if not already transitively available:
composer require php-http/guzzle5-adapter guzzlehttp/guzzle:^5.3
Inspect vendor/php-http/guzzle5-adapter/src/Client.php before wiring it up; this package predates modern documentation, and the installed minor version is the source of truth for constructor details and exception behavior.
The first practical use case is binding the adapter as Laravel’s Http\Client\HttpClient implementation:
use GuzzleHttp\Client as Guzzle5Client;
use Http\Adapter\Guzzle5\Client as Guzzle5Adapter;
use Http\Client\HttpClient;
public function register()
{
$this->app->singleton(HttpClient::class, function ($app) {
$guzzle = new Guzzle5Client([
'base_url' => rtrim($app['config']->get('services.legacy.base_url'), '/'),
'defaults' => [
'timeout' => 10,
'connect_timeout' => 5,
'verify' => true,
],
]);
return new Guzzle5Adapter($guzzle);
});
}
Send a first request through HTTPlug, not through Guzzle directly:
use GuzzleHttp\Psr7\Request;
use Http\Client\HttpClient;
class LegacyHealthCheck
{
private $http;
public function __construct(HttpClient $http)
{
$this->http = $http;
}
public function check(): bool
{
$response = $this->http->sendRequest(
new Request('GET', 'https://api.example.com/health', [
'Accept' => 'application/json',
])
);
return $response->getStatusCode() === 200;
}
}
Bind the HTTPlug interface, never the adapter. Application code should depend on Http\Client\HttpClient, so the legacy Guzzle 5 adapter can later be replaced by the Guzzle 7 adapter, a PSR-18 bridge, or Laravel’s HTTP client. Keep Guzzle 5 types in the service provider only.
Use named clients for different upstreams or timeouts. A PSR-7 request cannot carry Guzzle 5 request options such as timeout, verify, proxy, allow_redirects, or cookie configuration. Those must be set on the underlying Guzzle client. If two integrations need different behavior, register two bindings or two small wrapper classes:
$this->app->singleton('http.legacy.fast', function () {
return new Guzzle5Adapter(new Guzzle5Client([
'base_url' => config('services.legacy.base_url'),
'defaults' => ['timeout' => 3],
]));
});
$this->app->singleton('http.legacy.slow', function () {
return new Guzzle5Adapter(new Guzzle5Client([
'base_url' => config('services.reports.base_url'),
'defaults' => ['timeout' => 60],
]));
});
Then inject the specific client into the service that needs it, or use Laravel contextual binding.
Build requests with PSR-7 objects. Use GuzzleHttp\Psr7\Request, Uri, Stream, and Response; do not hand the adapter Guzzle 5 request objects. For JSON payloads:
$request = new \GuzzleHttp\Psr7\Request(
'POST',
rtrim(config('services.legacy.base_url'), '/').'/v1/orders',
[
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
json_encode($payload)
);
For query strings, prefer an explicit URI:
$uri = (new \GuzzleHttp\Psr7\Uri($baseUrl.'/v1/orders'))
->withQuery(http_build_query(['page' => 2, 'per_page' => 50]));
$request = new \GuzzleHttp\Psr7\Request('GET', $uri, [
'Accept' => 'application/json',
]);
Add HTTPlug plugins around the adapter when needed. Retries, default headers, logging, and authentication are easier through php-http/client-common than through Guzzle 5 event subscribers:
composer require php-http/client-common
use Http\Client\Common\PluginClient;
use Http\Client\Common\Plugin\HeaderDefaultsPlugin;
use Http\Client\Common\Plugin\RetryPlugin;
$http = new PluginClient($adapter, [
new HeaderDefaultsPlugin([
'User-Agent' => 'LegacyApp/1.0',
]),
new RetryPlugin(['retries' => 2]),
]);
This keeps retry and header policy out of domain code and makes the eventual adapter swap less invasive.
Integrate third-party libraries through the container. Older libraries such as legacy Elasticsearch or HTTP-based SDKs often type-hint Http\Client\HttpClient. In Laravel, bind that interface explicitly. If the library uses Http\Discovery\HttpClientDiscovery::find() instead of constructor injection, install or remove adapters carefully: discovery may select php-http/curl-client, php-http/socket-client, or another provider if present. In a Laravel app, explicit container binding is more predictable than discovery.
Handle HTTPlug exceptions, not Guzzle 5 exceptions. The adapter translates Guzzle failures into HTTPlug exceptions. Catch the HTTPlug types and use the response from HttpException when you need the status code or body:
use Http\Client\Exception\HttpException;
use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
try {
$response = $this->http->sendRequest($request);
} catch (HttpException $e) {
$response = $e->getResponse();
report($e);
// Log status and body; decide whether to rethrow.
} catch (NetworkException | RequestException $e) {
report($e);
throw $e;
}
Test by replacing the binding. Use php-http/mock-client in feature tests rather than spinning up Guzzle 5 mock handlers:
composer require --dev php-http/mock-client
use GuzzleHttp\Psr7\Response;
use Http\Client\HttpClient;
use Http\Mock\Client as MockClient;
$mock = new MockClient();
$mock->addResponse(new Response(
200,
['Content-Type' => 'application/json'],
'{"ok":true}'
));
$this->app->instance(HttpClient::class, $mock);
$this->get('/legacy-health')->assertOk();
Isolate the legacy client behind your own interface. Wrap the adapter in a small application-level class such as LegacyOrdersClient with methods like findOrder() and createOrder(). That class owns PSR-7 request construction and exception mapping. The rest of the Laravel app sees domain methods, not HTTPlug or Guzzle 5 concepts. This is the main day-to-day pattern that makes the legacy dependency survivable.
Archived and end-of-life. This package was last released in 2019 and is archived. Guzzle 5 is EOL and will not receive security fixes. Treat it as a temporary compatibility layer. New integrations should use php-http/guzzle7-adapter, Laravel’s Illuminate\Support\Facades\Http, or a maintained PSR-18 client.
Composer conflicts with modern Laravel. Laravel 8+ typically ships Guzzle 7. This adapter requires Guzzle 5. You cannot normally install both major versions in one Composer project. Do not downgrade the application’s Guzzle dependency casually. Check with:
composer why guzzlehttp/guzzle
composer why php-http/guzzle5-adapter
If another package requires php-http/client-implementation, Composer may satisfy it with a different adapter. Pin the adapter you intend to use, or bind explicitly in Laravel so discovery does not decide for you.
No per-request Guzzle options. This is the most common practical surprise. A PSR-7 request carries method, URI, headers, and body. It does not carry Guzzle 5’s timeout, verify, proxy, allow_redirects, cookies, or exceptions settings. Configure those on the underlying GuzzleHttp\Client. If a single upstream needs both a short and a long timeout, use two clients.
Guzzle 5 configuration style is old. Use base_url and defaults, not Guzzle 6/7’s base_uri and http_errors. Guzzle 5 exception behavior is controlled by exceptions; Guzzle 6/7 uses http_errors. Copying modern Guzzle configuration examples into this adapter’s client will silently do the wrong thing or fail.
Do not call Guzzle 5 response methods on the returned object. sendRequest() returns a PSR-7 ResponseInterface. There is no $response->json() from Guzzle 5 on it. Decode explicitly:
$data = json_decode((string) $response->getBody(), true);
Exception mapping is the boundary. Catch Http\Client\Exception\HttpException, NetworkException, and RequestException. Do not let GuzzleHttp\Exception\* leak into controllers or jobs. If the adapter’s status-code behavior surprises you, inspect the installed adapter source; HTTPlug-era adapters may throw HttpException for 4xx/5xx responses, whereas modern PSR-18 clients typically return those responses without throwing.
Streams and retries can bite. PSR-7 request bodies may be streams. If you use a retry plugin or Guzzle 5 retry logic, a non-seekable stream may already be consumed on the second attempt. For uploads, prefer a string body or a seekable stream, or avoid automatic retries for that request.
Use absolute URIs. Although Guzzle 5 supports base_url, PSR-7 request objects are clearer when given an absolute URI. Do not rely on relative PSR-7 paths plus base_url; it makes debugging and testing harder.
Queues and long-running workers. Resolve the client from the container inside the job rather than serializing it. The underlying Guzzle 5 client is reusable, which is good for queue workers, but configuration changes require a worker restart. Also remember that a singleton client keeps its connection state; if an upstream changes DNS or credentials at runtime, the worker may hold stale configuration.
Logging and extension points. For simple logging, wrap the adapter with an HTTPlug plugin. For deeper Guzzle 5 instrumentation, attach Guzzle 5 event subscribers to the underlying client, but keep that inside the service provider or a dedicated infrastructure class. Do not scatter Guzzle 5 event names through application code.
Plan the exit. The healthiest use of this package is a thin, well-tested compatibility layer with a scheduled migration. Replace it with Guzzle 7 or Laravel’s HTTP client as soon as the dependent library supports a modern php-http/client-implementation.
How can I help you explore Laravel packages today?