PHP Guzzle HTTP Client — Complete Guide to HTTP Requests
In this tutorial, you will learn about PHP Guzzle HTTP Client. We cover key concepts, practical examples, and best practices to help you master this topic.
PHP Guzzle HTTP is the most popular PHP HTTP client library, providing a clean interface for sending HTTP requests and handling responses, with support for asynchronous requests, middleware, file uploads, authentication, and retries. This tutorial covers installing Guzzle, making GET and POST requests, handling JSON APIs, configuring headers and query parameters, managing cookies and sessions, uploading files, handling errors with exceptions, using asynchronous requests for concurrent API calls, and building custom middleware for logging and rate limiting.
What You'll Learn
- Installing Guzzle via Composer and understanding its architecture
- Sending GET, POST, PUT, DELETE requests with Guzzle Client
- Handling JSON request bodies and Parsing JSON responses
- Configuring headers, query parameters, and timeouts
- Uploading files using multipart form data
- Handling HTTP errors with GuzzleException
- Making asynchronous requests with promises
- Using middleware for logging, retries, and rate limiting
- Authenticating with Basic Auth, Bearer tokens, and OAuth
Why It Matters
Virtually every modern PHP application communicates with external services: payment gateways, social media APIs, cloud storage, third-party data sources. Using PHP's native file_get_contents or curl directly is verbose and error-prone. Guzzle provides a consistent, testable, and feature-rich interface that handles edge cases like connection timeouts, redirects, and streaming responses automatically.
Real-World Use
Doda Browser's weather widget uses Guzzle to fetch forecast data from the OpenWeatherMap API. The asynchronous request feature lets the widget fetch current conditions, hourly forecast, and weekly forecast concurrently, reducing total load time from 3 seconds to 800 milliseconds. Guzzle's middleware pipeline handles rate limiting and automatic retries when the API responds with 429 Too Many Requests.
Learning Path
flowchart LR A[WebSockets] --> B[Guzzle HTTP\nYou are here] B --> C[PHP Ecosystem] style B fill:#f90,color:#fff
Installing Guzzle
composer require guzzlehttp/guzzle
Guzzle requires PHP 8.1+ and the JSON and cURL extensions.
Basic GET Request
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
$client = new Client();
try {
$response = $client->get('https://api.github.com/users/octocat', [
'headers' => [
'Accept' => 'application/vnd.github.v3+json',
'User-Agent' => 'GuzzleDemo/1.0',
],
'timeout' => 5.0,
]);
$statusCode = $response->getStatusCode();
$body = json_decode($response->getBody(), true);
echo "Status: $statusCode\n";
echo "Login: " . $body['login'] . "\n";
echo "Name: " . ($body['name'] ?? 'N/A') . "\n";
echo "Public Repos: " . $body['public_repos'] . "\n";
} catch (GuzzleException $e) {
echo "HTTP Error: " . $e->getMessage() . "\n";
}
Output:
Status: 200
Login: octocat
Name: The Octocat
Public Repos: 8
POST Request with JSON Body
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
$client = new Client([
'base_uri' => 'https://jsonplaceholder.typicode.com',
'timeout' => 10.0,
]);
try {
$response = $client->post('/posts', [
'json' => [
'title' => 'My Guzzle Post',
'body' => 'This post was created using Guzzle HTTP client.',
'userId' => 1,
],
]);
$body = json_decode($response->getBody(), true);
echo "Status: " . $response->getStatusCode() . "\n";
echo "Created Post ID: " . $body['id'] . "\n";
echo "Title: " . $body['title'] . "\n";
} catch (GuzzleException $e) {
echo "Error: " . $e->getMessage() . "\n";
}
Output:
Status: 201
Created Post ID: 101
Title: My Guzzle Post
Query Parameters and Headers
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://api.github.com',
'headers' => [
'Accept' => 'application/vnd.github.v3+json',
'User-Agent' => 'GuzzleDemo/1.0',
],
]);
$response = $client->get('/search/repositories', [
'query' => [
'q' => 'php guzzle',
'sort' => 'stars',
'order' => 'desc',
'per_page' => 5,
],
]);
$body = json_decode($response->getBody(), true);
echo "Search results for 'php guzzle':\n";
foreach ($body['items'] as $repo) {
printf("- %s (%d stars, %d forks)\n", $repo['full_name'], $repo['stargazers_count'], $repo['forks_count']);
}
Output:
Search results for 'php guzzle':
- guzzle/guzzle (23200 stars, 2840 forks)
- graham-campbell/guzzle-factory (200 stars, 24 forks)
- ...
File Upload with Multipart
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
$client = new Client();
try {
$response = $client->post('https://httpbin.org/post', [
'multipart' => [
[
'name' => 'file',
'contents' => fopen('/path/to/document.pdf', 'r'),
'filename' => 'document.pdf',
],
[
'name' => 'description',
'contents' => 'Uploaded via Guzzle',
],
],
]);
$body = json_decode($response->getBody(), true);
echo "Uploaded file: " . $body['files']['file'] . "\n";
echo "Description: " . $body['form']['description'] . "\n";
} catch (GuzzleException $e) {
echo "Upload failed: " . $e->getMessage() . "\n";
}
Asynchronous Requests
Guzzle uses promises for concurrent requests:
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use GuzzleHttp\Exception\GuzzleException;
$client = new Client(['timeout' => 10.0]);
$promises = [
'users' => $client->getAsync('https://jsonplaceholder.typicode.com/users'),
'posts' => $client->getAsync('https://jsonplaceholder.typicode.com/posts'),
'todos' => $client->getAsync('https://jsonplaceholder.typicode.com/todos'),
];
$start = microtime(true);
$results = Promise\Utils::settle($promises)->wait();
$elapsed = (microtime(true) - $start) * 1000;
foreach ($results as $key => $result) {
if ($result['state'] === 'fulfilled') {
$body = json_decode($result['value']->getBody(), true);
echo "$key: " . count($body) . " items\n";
} else {
echo "$key: failed - " . $result['reason']->getMessage() . "\n";
}
}
echo "Total time: " . round($elapsed, 0) . " ms\n";
Output:
users: 10 items
posts: 100 items
todos: 200 items
Total time: 312 ms
Authentication
Basic Auth:
<?php
use GuzzleHttp\Client;
$client = new Client();
$response = $client->post('https://api.example.com/auth', [
'auth' => ['username', 'password'],
'json' => ['scope' => 'read'],
]);
Bearer Token:
<?php
use GuzzleHttp\Client;
$client = new Client([
'headers' => [
'Authorization' => 'Bearer eyJhbGciOiJIUzI1NiIs...',
],
]);
$response = $client->get('https://api.example.com/me');
Middleware for Retries and Logging
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\ResponseInterface;
$handlerStack = HandlerStack::create();
// Retry middleware
$handlerStack->push(Middleware::retry(function (
int $retries,
Request $request,
?ResponseInterface $response,
?\Throwable $exception
) {
if ($retries >= 3) return false;
if ($response && $response->getStatusCode() === 429) return true;
if ($exception) return true;
return false;
}, function (int $retries) {
return 1000 * pow(2, $retries);
}));
// Logging middleware
$handlerStack->push(Middleware::mapRequest(function (Request $request) {
echo "Request: " . $request->getMethod() . " " . $request->getUri() . "\n";
return $request;
}));
$client = new Client([
'handler' => $handlerStack,
'timeout' => 5.0,
]);
$response = $client->get('https://httpbin.org/delay/1');
echo "Status: " . $response->getStatusCode() . "\n";
Output:
Request: GET https://httpbin.org/delay/1
Status: 200
Common Mistakes
- Not handling exceptions: Guzzle throws exceptions for 4xx and 5xx responses. Always wrap requests in try-catch blocks.
- Ignoring the response body stream: The response body is a stream. Call getContents() or cast to string to read it. Reading it twice requires rewinding.
- Not setting timeouts: The default timeout is 0 (no timeout). Set connect_timeout and timeout to prevent hanging requests.
- Sending synchronous requests when async is appropriate: Sequential API calls add latency. Use async requests with Promise\Utils::settle for independent requests.
- Hardcoding base URIs: Use the base_uri option to avoid repeating the domain. Override with full URIs when needed.
Practice Questions
How do you send a JSON body in a POST request?
- Use the 'json' option. Guzzle automatically sets Content-Type to application/json and encodes the array.
What is the difference between get and getAsync?
- get() blocks until the response is received. getAsync() returns a promise that resolves when the response arrives, allowing other code to run in between.
How do you handle a 404 error in Guzzle?
- Wrap the request in a try-catch that catches GuzzleHttp\Exception\ClientException, which is thrown for 4xx responses.
What is the purpose of HandlerStack in Guzzle?
- HandlerStack is the middleware pipeline. Middleware can inspect, modify, or retry requests and responses. It is used for logging, Caching, authentication, and rate limiting.
Challenge: Build a PHP script that fetches data from three APIs concurrently using Guzzle promises, processes the results, and saves them to a JSON file. The three APIs should be different types: a paginated list, a single resource, and a search query.
Mini Project
Build a GitHub API client using Guzzle:
- Create a GithubClient class that wraps Guzzle and authenticates with a personal access token.
- Implement methods: getUser, getRepos, createRepo, searchCode, getReadme.
- Handle pagination for endpoints that return multiple pages.
- Implement rate limit checking: warn when the remaining requests drop below 10.
- Cache Repository data in a local file for 5 minutes to reduce API calls.
- Output results as formatted Markdown.
FAQ
What's Next
Now that you can communicate with any HTTP service, explore the full PHP Ecosystem of tools, frameworks, and libraries available for modern PHP development.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro