← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Guzzle HTTP Client

PHP PHP 7.2+ Intermediate
debt(d5/e3/b5/t5)
d5 Detectability Operational debt — how invisible misuse is to your safety net

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.

e3 Effort Remediation debt — work required to fix once spotted

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.

b5 Burden Structural debt — long-term weight of choosing wrong

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).

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

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.

About DEBT scoring →

Also Known As

Guzzle GuzzleHttp PHP HTTP client PSR-18

TL;DR

Guzzle is PHP's most popular HTTP client library — providing a clean API for making synchronous and asynchronous HTTP requests, handling middleware, retries, authentication, and multipart uploads, with PSR-7 and PSR-18 compliance.

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

Guzzle is always needed for HTTP requests. For simple one-off requests, file_get_contents() with a stream context or curl_exec() is fine and requires no dependency. Use Guzzle when you need middleware, retries, async requests, or a testable/mockable interface.

Why It Matters

Almost every PHP application makes outbound HTTP requests — calling payment APIs, weather services, internal microservices, or LLM endpoints. Guzzle provides retry logic, connection pooling, middleware, and async requests that curl_exec() requires hundreds of lines to replicate. It is also the de facto standard: Laravel's HTTP facade wraps Guzzle, and most PHP SDK packages use it as their transport.

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

✗ Vulnerable
<?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
✓ Fixed
<?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

Added 23 Mar 2026
Views 40
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 1 ping W 0 pings T 0 pings F 0 pings S 1 ping S 1 ping M 1 ping T 0 pings W 0 pings T 1 ping F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 2 pings T 0 pings F 1 ping S 1 ping S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Amazonbot 7 Google 4 ChatGPT 3 Ahrefs 3 Scrapy 3 Perplexity 2 SEMrush 2 Claude 1 Bing 1 Meta AI 1 PetalBot 1
crawler 26 crawler_json 2
DEV INTEL Tools & Severity
⚙ Fix effort: Low
⚡ Quick Fix
For HTTP requests in tests, use MockHandler: $mock = new MockHandler([new Response(200, [], json_encode($data))]); $client = new Client(['handler' => HandlerStack::create($mock)]); — no real network calls needed.
📦 Applies To
PHP 7.2+ web cli laravel symfony


✓ schema.org compliant