Guzzle HTTP Client
debt(d5/e3/b5/t5)
Closest to 'specialist tool catches it' (d5). Missing timeouts, uncaught RequestException, or creating new Client instances per request are not caught by syntax checks or default linters. Static analysis tools like PHPStan or Psalm with strict rules can catch some type issues, but the common mistakes (no timeout, wrong exception type, per-request instantiation) typically surface only in runtime testing or code review. No detection tools specified in the term metadata.
Closest to 'simple parameterised fix' (e3). The quick_fix shows mocking is a one-liner with MockHandler, and adding timeouts is a simple config array change. However, fixing the 'new Client per request' antipattern requires refactoring to dependency injection or a singleton, which touches multiple call sites but stays within one component. Catching the right exception types is a find-and-replace. Overall a modest refactor, not a one-liner but not cross-cutting.
Closest to 'persistent productivity tax' (b5). Guzzle applies to web and CLI contexts and becomes load-bearing once adopted — most HTTP calls flow through it. The choice of Guzzle over native curl or other clients shapes testing strategy (MockHandler), error handling patterns, and DI configuration across the codebase. Not quite 'defines the system's shape' (b9), but it creates ongoing friction if misused (e.g., per-request instantiation, inconsistent timeout policies).
Closest to 'notable trap' (t5). The misconception is that Guzzle is always needed — many developers reach for it when file_get_contents() or curl_exec() would suffice. The common mistakes (no timeout defaults, catching \Exception instead of RequestException, per-request Client creation) are documented gotchas that most developers eventually learn but initially assume wrong behavior based on simpler HTTP libraries.
Also Known As
TL;DR
Explanation
Guzzle wraps PHP's cURL extension and stream wrappers with a clean object-oriented interface. A Client instance is configured with a base URI and default options; requests are made with get(), post(), request(), or sendAsync() for async operations. Middleware stacks (HandlerStack) allow composable request/response modification: logging, retries, authentication headers, and caching. Guzzle is PSR-7 compliant (RequestInterface, ResponseInterface) and PSR-18 compliant (ClientInterface), making it swappable for testing. The Symfony HttpClient component is a modern alternative with tighter Symfony integration; for simpler use cases, PHP's built-in file_get_contents with stream contexts or curl_exec suffice.
Common Misconception
Why It Matters
Common Mistakes
- Not setting a timeout — without a timeout, Guzzle waits indefinitely; always set 'timeout' and 'connect_timeout'.
- Not mocking Guzzle in tests — real HTTP calls in tests are slow and flaky; always inject a MockHandler or use a PSR-18 compatible mock.
- Catching \Exception instead of RequestException — Guzzle throws RequestException (4xx/5xx) and ConnectException (network failure); catch them separately.
- Creating a new Client instance per request — instantiating a Client is cheap but the underlying connection pool is per-instance; create one shared instance (singleton or DI).
Code Examples
<?php
// ❌ Manual curl — verbose, no retry, no error handling
$ch = curl_init('https://api.example.com/users');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $token]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) { /* manual error handling */ }
$data = json_decode($body, true); // Breaks silently on network error
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
// ✅ Guzzle — clean, retry-able, mockable
$client = new Client([
'base_uri' => 'https://api.example.com',
'timeout' => 5.0,
'headers' => ['Authorization' => 'Bearer ' . $token],
]);
try {
$response = $client->get('/users', [
'query' => ['page' => 1, 'limit' => 50],
]);
$users = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
} catch (RequestException $e) {
$statusCode = $e->getResponse()?->getStatusCode();
Log::error('API call failed', ['status' => $statusCode, 'message' => $e->getMessage()]);
throw new ApiException('Failed to fetch users', previous: $e);
}
// ✅ Retry middleware — automatic retry on 5xx and network errors
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
$stack = HandlerStack::create();
$stack->push(Middleware::retry(function ($retries, Request $req, $res, $exc) {
return $retries < 3 && ($exc !== null || $res->getStatusCode() >= 500);
}, fn($retries) => 1000 * 2 ** $retries)); // Exponential backoff