PHPackages                             jerome/fetch-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. jerome/fetch-php

ActiveLibrary[HTTP &amp; Networking](/categories/http)

jerome/fetch-php
================

The JavaScript fetch API for PHP.

3.4.1(4mo ago)4467.7k↓26.9%26[3 issues](https://github.com/Thavarshan/fetch-php/issues)[1 PRs](https://github.com/Thavarshan/fetch-php/pulls)1MITPHPPHP ^8.3CI passing

Since Sep 13Pushed 3mo ago6 watchersCompare

[ Source](https://github.com/Thavarshan/fetch-php)[ Packagist](https://packagist.org/packages/jerome/fetch-php)[ Docs](https://fetch-php.thavarshan.com)[ Fund](https://www.buymeacoffee.com/thavarshan)[ GitHub Sponsors](https://github.com/thavarshan)[ RSS](/packages/jerome-fetch-php/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (15)Versions (28)Used By (1)

Fetch PHP
=========

[](#fetch-php)

[![Latest Version on Packagist](https://camo.githubusercontent.com/001e816053c484f58fc00769e2d61407875b9943fba74c4cafa02938cece4570/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a65726f6d652f66657463682d7068702e737667)](https://packagist.org/packages/jerome/fetch-php)[![CI](https://github.com/Thavarshan/fetch-php/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Thavarshan/fetch-php/actions/workflows/ci.yml)[![Codecov](https://camo.githubusercontent.com/ef7faf831f751e02bb76fd3f093a4f45ab37403f43e627a3d1a7311c9c9a895b/68747470733a2f2f636f6465636f762e696f2f67682f5468617661727368616e2f66657463682d7068702f6272616e63682f6d61696e2f67726170682f62616467652e737667)](https://codecov.io/gh/Thavarshan/fetch-php)[![CodeQL](https://github.com/Thavarshan/fetch-php/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/Thavarshan/fetch-php/actions/workflows/github-code-scanning/codeql)[![PHPStan](https://camo.githubusercontent.com/b6d441ad4fe8332cb16c72aa27f22cc685181dfd74ae34964afc92c6c1146b3c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c2532306d61782d627269676874677265656e2e737667)](https://phpstan.org/)[![PHP Version](https://camo.githubusercontent.com/01353996c228c22ed3ffeeff74844d56a22661238aae18c8c3e9732a63677078/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6a65726f6d652f66657463682d7068702e737667)](https://packagist.org/packages/jerome/fetch-php)[![License](https://camo.githubusercontent.com/0e0b84e5a8bc540788f91a976b46a5d43a8ab596287c9b3dda90ebf43a3980d5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6a65726f6d652f66657463682d7068702e737667)](https://packagist.org/packages/jerome/fetch-php)[![Total Downloads](https://camo.githubusercontent.com/b62da4e05a51663d410a64fd028f26d033509332d74b3df06ae3c5c6314485b1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6a65726f6d652f66657463682d7068702e737667)](https://packagist.org/packages/jerome/fetch-php)[![GitHub Stars](https://camo.githubusercontent.com/219749adce9c759f9c3723e19e26608a58eb057d9f623e7cf43575e2016dc8f0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f5468617661727368616e2f66657463682d7068702e7376673f7374796c653d736f6369616c266c6162656c3d5374617273)](https://github.com/Thavarshan/fetch-php/stargazers)

**Fetch PHP** is a modern HTTP client library for PHP that brings JavaScript's `fetch` API experience to PHP. Built on top of Guzzle, Fetch PHP allows you to write HTTP code with a clean, intuitive JavaScript-like syntax while still maintaining PHP's familiar patterns.

With support for both synchronous and asynchronous requests, a fluent chainable API, and powerful retry mechanics, Fetch PHP streamlines HTTP operations in your PHP applications.

Full documentation can be found [here](https://fetch-php.thavarshan.com/)

---

Key Features
------------

[](#key-features)

- **JavaScript-like Syntax**: Write HTTP requests just like you would in JavaScript with the `fetch()` function and `async`/`await` patterns
- **Promise-based API**: Use familiar `.then()`, `.catch()`, and `.finally()` methods for async operations
- **Fluent Interface**: Build requests with a clean, chainable API
- **Built on Guzzle**: Benefit from Guzzle's robust functionality with a more elegant API
- **Retry Mechanics**: Configurable retry logic with exponential backoff for transient failures
- **RFC 7234 HTTP Caching**: Full caching support with ETag/Last-Modified revalidation, stale-while-revalidate, and stale-if-error
- **Connection Pooling**: Reuse TCP connections across requests with global connection pool and DNS caching
- **HTTP/2 Support**: Native HTTP/2 protocol support for improved performance
- **Debug &amp; Profiling**: Built-in debugging and performance profiling capabilities
- **Type-Safe Enums**: Modern PHP 8.3+ enums for HTTP methods, content types, and status codes
- **Testing Utilities**: Built-in mock responses and request recording for testing
- **PHP-style Helper Functions**: Includes traditional PHP function helpers (`get()`, `post()`, etc.) for those who prefer that style
- **PSR Compliant**: Implements PSR-7 (HTTP Messages), PSR-18 (HTTP Client), and PSR-3 (Logger) standards

Why Choose Fetch PHP?
---------------------

[](#why-choose-fetch-php)

### Beyond Guzzle

[](#beyond-guzzle)

While Guzzle is a powerful HTTP client, Fetch PHP enhances the experience by providing:

- **JavaScript-like API**: Enjoy the familiar `fetch()` API and `async`/`await` patterns from JavaScript
- **Global client management**: Configure once, use everywhere with the global client
- **Simplified requests**: Make common HTTP requests with less code
- **Enhanced error handling**: Reliable retry mechanics and clear error information
- **Type-safe enums**: Use enums for HTTP methods, content types, and status codes

FeatureFetch PHPGuzzleAPI StyleJavaScript-like fetch + async/await + PHP-style helpersPHP-style onlyClient ManagementGlobal client + instance optionsInstance-based onlyRequest SyntaxClean, minimalMore verboseTypesModern PHP 8.3+ enumsString constantsHelper FunctionsMultiple styles availableLimitedInstallation
------------

[](#installation)

```
composer require jerome/fetch-php
```

> **Requirements**: PHP 8.3 or higher

Basic Usage
-----------

[](#basic-usage)

### JavaScript-style API (Promise Chaining)

[](#javascript-style-api-promise-chaining)

```
use function Matrix\Support\async;

// JavaScript-like promise chaining in PHP
async(fn() => fetch('https://api.example.com/users'))
    ->then(fn ($response) => $response->json())
    ->catch(fn ($error) => echo "Error: " . $error->getMessage())
    ->finally(fn () => echo "Request completed.");
```

Or, using the client handler for more control:

```
$handler = fetch_client()->getHandler();
$handler->async();

$handler->get('https://api.example.com/users')
    ->then(fn ($response) => $response->json())
    ->catch(fn ($error) => echo "Error: " . $error->getMessage())
    ->finally(fn () => echo "Request completed.");
```

### PHP-style Helpers

[](#php-style-helpers)

```
// GET request with query parameters
$response = get('https://api.example.com/users', ['page' => 1, 'limit' => 10]);

// POST request with JSON data
$response = post('https://api.example.com/users', [
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);
```

### Fluent API

[](#fluent-api)

```
// Chain methods to build your request
$response = fetch_client()
    ->baseUri('https://api.example.com')
    ->withHeaders(['Accept' => 'application/json'])
    ->withToken('your-auth-token')
    ->withQueryParameters(['page' => 1, 'limit' => 10])
    ->get('/users');
```

Async/Await Pattern
-------------------

[](#asyncawait-pattern)

> **Note**: The async functions (`async`, `await`, `all`, `race`, `map`, `batch`, `retry`) are provided by the [jerome/matrix](https://packagist.org/packages/jerome/matrix) library, which is included as a dependency.

### Using Async/Await

[](#using-asyncawait)

```
use function Matrix\Support\async;
use function Matrix\Support\await;

$response = await(async(fn() => fetch('https://api.example.com/users')));
$users = $response->json();
echo "Fetched " . count($users) . " users";
```

### Multiple Concurrent Requests with Async/Await

[](#multiple-concurrent-requests-with-asyncawait)

```
// These async functions are provided by the Matrix library dependency
use function Matrix\Support\async;
use function Matrix\Support\await;
use function Matrix\Support\all;

// Execute an async function
await(async(function() {
    // Create multiple requests
    $results = await(all([
        'users' => async(fn() => fetch('https://api.example.com/users')),
        'posts' => async(fn() => fetch('https://api.example.com/posts')),
        'comments' => async(fn() => fetch('https://api.example.com/comments'))
    ]));

    // Process the results
    $users = $results['users']->json();
    $posts = $results['posts']->json();
    $comments = $results['comments']->json();

    echo "Fetched " . count($users) . " users, " .
         count($posts) . " posts, and " .
         count($comments) . " comments";
}));
```

### Sequential Requests with Async/Await

[](#sequential-requests-with-asyncawait)

```
use function Matrix\Support\async;
use function Matrix\Support\await;

await(async(function() {
    // First request: get auth token
    $authResponse = await(async(fn() =>
        fetch('https://api.example.com/auth/login', [
            'method' => 'POST',
            'json' => [
                'username' => 'user',
                'password' => 'pass'
            ]
        ])
    ));

    $token = $authResponse->json()['token'];

    // Second request: use token to get user data
    $userResponse = await(async(fn() =>
        fetch('https://api.example.com/me', [
            'token' => $token
        ])
    ));

    return $userResponse->json();
}));
```

### Error Handling with Async/Await

[](#error-handling-with-asyncawait)

```
use function Matrix\Support\async;
use function Matrix\Support\await;

try {
    $data = await(async(function() {
        $response = await(async(fn() =>
            fetch('https://api.example.com/users/999')
        ));

        if ($response->isNotFound()) {
            throw new \Exception("User not found");
        }

        return $response->json();
    }));

    // Process the data

} catch (\Exception $e) {
    echo "Error: " . $e->getMessage();
}
```

Traditional Promise-based Pattern
---------------------------------

[](#traditional-promise-based-pattern)

```
// Set up an async request
// Get the handler for async operations
$handler = fetch_client()->getHandler();
$handler->async();

// Make the async request
$promise = $handler->get('https://api.example.com/users');

// Handle the result with callbacks
$promise->then(
    function ($response) {
        // Process successful response
        $users = $response->json();
        foreach ($users as $user) {
            echo $user['name'] . PHP_EOL;
        }
    },
    function ($exception) {
        // Handle errors
        echo "Error: " . $exception->getMessage();
    }
);
```

Advanced Async Usage
--------------------

[](#advanced-async-usage)

### Concurrent Requests with Promise Utilities

[](#concurrent-requests-with-promise-utilities)

```
use function Matrix\Support\race;

// Create promises for redundant endpoints
$promises = [
    async(fn() => fetch('https://api1.example.com/data')),
    async(fn() => fetch('https://api2.example.com/data')),
    async(fn() => fetch('https://api3.example.com/data'))
];

// Get the result from whichever completes first
$response = await(race($promises));
$data = $response->json();
echo "Got data from the fastest source";
```

### Controlled Concurrency with Map

[](#controlled-concurrency-with-map)

```
use function Matrix\Support\map;

// List of user IDs to fetch
$userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Process at most 3 requests at a time
$responses = await(map($userIds, function($id) {
    return async(function() use ($id) {
        return fetch("https://api.example.com/users/{$id}");
    });
}, 3));

// Process the responses
foreach ($responses as $index => $response) {
    $user = $response->json();
    echo "Processed user {$user['name']}\n";
}
```

### Batch Processing

[](#batch-processing)

```
use function Matrix\Support\batch;

// Array of items to process
$items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Process in batches of 3 with max 2 concurrent batches
$results = await(batch(
    $items,
    function($batch) {
        // Process a batch
        return async(function() use ($batch) {
            $batchResults = [];
            foreach ($batch as $id) {
                $response = await(async(fn() =>
                    fetch("https://api.example.com/users/{$id}")
                ));
                $batchResults[] = $response->json();
            }
            return $batchResults;
        });
    },
    3, // batch size
    2  // concurrency
));
```

### With Retries

[](#with-retries)

```
use function Matrix\Support\retry;

// Retry a flaky request up to 3 times with exponential backoff
$data = await(retry(
    function() {
        return async(function() {
            return fetch('https://api.example.com/unstable-endpoint');
        });
    },
    3, // max attempts
    function($attempt) {
        // Exponential backoff strategy
        return min(pow(2, $attempt) * 100, 1000);
    }
));
```

Advanced Configuration
----------------------

[](#advanced-configuration)

### Automatic Retries

[](#automatic-retries)

Fetch PHP automatically retries transient failures with exponential backoff.

- Default: 1 retry attempt (`ClientHandler::DEFAULT_RETRIES`) with a 100 ms base delay
- Default delay: 100 ms base with exponential backoff (when retries configured)
- Retry triggers:
    - Network/connect errors (e.g., ConnectException)
    - HTTP status codes: 408, 429, 500, 502, 503, 504, 507, 509, 520-523, 525, 527, 530 (customizable)

Configure per-request:

```
$response = fetch_client()
    ->retry(3, 200)                // 3 retries, 200ms base delay
    ->retryStatusCodes([429, 503]) // optional: customize which statuses retry
    ->retryExceptions([ConnectException::class]) // optional: customize exception types
    ->get('https://api.example.com/unstable');
```

Notes:

- HTTP error statuses do not throw; you receive the response. Retries happen internally when configured.
- Network failures are retried and, if all attempts fail, throw a `Fetch\Exceptions\RequestException`.

### Authentication

[](#authentication)

```
// Basic auth
$response = fetch('https://api.example.com/secure', [
    'auth' => ['username', 'password']
]);

// Bearer token
$response = fetch_client()
    ->withToken('your-oauth-token')
    ->get('https://api.example.com/secure');
```

### Proxies

[](#proxies)

```
$response = fetch('https://api.example.com', [
    'proxy' => 'http://proxy.example.com:8080'
]);

// Or with fluent API
$response = fetch_client()
    ->withProxy('http://proxy.example.com:8080')
    ->get('https://api.example.com');
```

### Global Client Configuration

[](#global-client-configuration)

```
// Configure once at application bootstrap
fetch_client([
    'base_uri' => 'https://api.example.com',
    'headers' => [
        'User-Agent' => 'MyApp/1.0',
        'Accept' => 'application/json',
    ],
    'timeout' => 10,
]);

// Use the configured client throughout your application
function getUserData($userId) {
    return fetch_client()->get("/users/{$userId}")->json();
}

function createUser($userData) {
    return fetch_client()->post('/users', $userData)->json();
}
```

Working with Responses
----------------------

[](#working-with-responses)

```
$response = fetch('https://api.example.com/users/1');

// Check if request was successful
if ($response->successful()) {
    // HTTP status code
    echo $response->getStatusCode(); // 200

    // Response body as JSON (returns array by default)
    $user = $response->json();

    // Response body as object
    $userObject = $response->object();

    // Response body as array
    $userArray = $response->array();

    // Response body as string
    $body = $response->text();

    // Get a specific header
    $contentType = $response->getHeaderLine('Content-Type');

    // Check status code categories
    if ($response->isSuccess()) {
        echo "Request succeeded (2xx)";
    }

    if ($response->isOk()) {
        echo "Request returned 200 OK";
    }

    if ($response->isNotFound()) {
        echo "Resource not found (404)";
    }
}

// ArrayAccess support
$name = $response['name']; // Access JSON response data directly

// Inspect retry-related statuses explicitly if needed
if ($response->getStatusCode() === 429) {
    // Handle rate limit response
}

## Working with Type-Safe Enums

```php
use Fetch\Enum\Method;
use Fetch\Enum\ContentType;
use Fetch\Enum\Status;

// Use enums for HTTP methods
$client = fetch_client();
$response = $client->request(Method::POST, '/users', $userData);

// Check HTTP status with enums
if ($response->statusEnum() === Status::OK) {
    // Process successful response
}

// Or use the isStatus helper
if ($response->isStatus(Status::OK)) {
    // Process successful response
}

// Content type handling
$response = $client->withBody($data, ContentType::JSON)->post('/users');
```

Error Handling
--------------

[](#error-handling)

```
// Synchronous error handling
try {
    $response = fetch('https://api.example.com/nonexistent');

    if (!$response->successful()) {
        echo "Request failed with status: " . $response->getStatusCode();
    }
} catch (\Throwable $e) {
    echo "Exception: " . $e->getMessage();
}

// Asynchronous error handling
$handler = fetch_client()->getHandler();
$handler->async();

$promise = $handler->get('https://api.example.com/nonexistent')
    ->then(function ($response) {
        if ($response->successful()) {
            return $response->json();
        }
        throw new \Exception("Request failed with status: " . $response->getStatusCode());
    })
    ->catch(function (\Throwable $e) {
        echo "Error: " . $e->getMessage();
    });
```

### Timeouts

[](#timeouts)

Control both total request timeout and connection timeout:

```
$response = fetch('https://api.example.com/data', [
    'timeout' => 15,          // total request timeout (seconds)
    'connect_timeout' => 5,   // connection timeout (seconds)
]);
```

If `connect_timeout` is not provided, it defaults to the `timeout` value.

### Logging and Redaction

[](#logging-and-redaction)

When request/response logging is enabled via a logger, sensitive values are redacted:

- Headers: Authorization, X-API-Key, API-Key, X-Auth-Token, Cookie, Set-Cookie
- Options: `auth` credentials

Logged context includes method, URI, selected options (sanitized), status code, duration, and content length.

Caching (sync-only)
-------------------

[](#caching-sync-only)

> **Note:** Caching is available for synchronous requests only. Async requests intentionally bypass the cache.

Fetch PHP implements RFC 7234-aware HTTP caching with ETag/Last-Modified revalidation, `stale-while-revalidate`, and `stale-if-error` support. The default backend is an in-memory cache (`MemoryCache`), but you can use `FileCache` or implement your own backend via `CacheInterface`.

### Cache Behavior

[](#cache-behavior)

- **Cacheable methods by default**: `GET`, `HEAD`
- **Cacheable status codes**: 200, 203, 204, 206, 300, 301, 404, 410 (RFC 7234 defaults)
- **Cache-Control headers respected**: `no-store`, `no-cache`, `max-age`, `s-maxage`, etc.
- **Revalidation**: Automatically adds `If-None-Match` (ETag) and `If-Modified-Since` (Last-Modified) headers for stale entries
- **304 Not Modified**: Merges headers and returns cached body
- **Vary headers**: Supports cache variance by headers (default: Accept, Accept-Encoding, Accept-Language)

### Basic Cache Setup

[](#basic-cache-setup)

```
use Fetch\Cache\MemoryCache;
use Fetch\Cache\FileCache;

$handler = fetch_client()->getHandler();

// Enable cache with in-memory backend (default)
$handler->withCache();

// Or use file-based cache
$handler->withCache(new FileCache('/path/to/cache'));

// Disable cache
$handler->withoutCache();

$response = $handler->get('https://api.example.com/users');
```

### Advanced Cache Configuration

[](#advanced-cache-configuration)

```
$handler->withCache(null, [
    'default_ttl' => 3600,                  // Default TTL in seconds (overridden by Cache-Control)
    'respect_cache_headers' => true,        // Honor Cache-Control headers (default: true)
    'is_shared_cache' => false,             // Act as shared cache (respects s-maxage)
    'stale_while_revalidate' => 60,         // Serve stale for 60s while revalidating
    'stale_if_error' => 300,                // Serve stale for 300s if backend fails
    'vary_headers' => ['Accept', 'Accept-Language'], // Headers to vary cache by
    'cache_methods' => ['GET', 'HEAD'],     // Cacheable HTTP methods
    'cache_status_codes' => [200, 301],     // Cacheable status codes
]);
```

### Per-Request Cache Control

[](#per-request-cache-control)

```
// Force a fresh request (bypass cache)
$response = $handler->withOptions(['cache' => ['force_refresh' => true]])
    ->get('https://api.example.com/users');

// Custom TTL for specific request
$response = $handler->withOptions(['cache' => ['ttl' => 600]])
    ->get('https://api.example.com/users');

// Custom cache key
$response = $handler->withOptions(['cache' => ['key' => 'custom:users']])
    ->get('https://api.example.com/users');

// Cache POST/PUT payloads (requires allowing the method globally)
$handler->withCache(null, [
    'cache_methods' => ['GET', 'HEAD', 'POST'],
]);
$report = $handler->withOptions([
    'cache' => [
        'ttl' => 120,
        'cache_body' => true, // include the JSON body in the cache key
    ],
])->post('https://api.example.com/reports', ['range' => 'weekly']);

Useful patterns:

- **Force refresh**: set `force_refresh => true` on the request to ignore stored entries.
- **Cache POST/PUT**: allow the verb in `cache_methods` via `withCache()` and set `cache_body => true` so the request body participates in the cache key.
- **Static assets**: pin a custom `key` for predictable lookups regardless of URL params.
```

Connection Pooling &amp; HTTP/2
-------------------------------

[](#connection-pooling--http2)

Connection pooling enables reuse of TCP connections across multiple requests, reducing latency and improving performance. The pool is **shared globally** across all handler instances, and includes DNS caching for faster lookups.

### Enable Connection Pooling

[](#enable-connection-pooling)

```
$handler = fetch_client()->getHandler();

// Enable with default settings
$handler->withConnectionPool(true);

// Or configure with custom options
$handler->withConnectionPool([
    'enabled' => true,
    'max_connections' => 50,        // Total connections across all hosts
    'max_per_host' => 10,           // Max connections per host
    'max_idle_per_host' => 5,       // Idle sockets kept per host
    'keep_alive_timeout' => 60,     // Connection lifetime in seconds
    'connection_timeout' => 5,      // Dial timeout in seconds
    'dns_cache_ttl' => 300,         // DNS cache TTL in seconds
    'connection_warmup' => false,
    'warmup_connections' => 0,
]);
```

### Enable HTTP/2

[](#enable-http2)

```
// Enable HTTP/2 (requires curl with HTTP/2 support)
$handler->withHttp2(true);

// Or configure with options
$handler->withHttp2([
    'enabled' => true,
    // Additional HTTP/2 configuration options...
]);
```

### Pool Management

[](#pool-management)

```
// Get pool statistics
$stats = $handler->getPoolStats();
// Returns: connections_created, connections_reused, total_requests, average_latency, reuse_rate

// Close all active connections
$handler->closeAllConnections();

// Reset pool and DNS cache (useful for testing)
$handler->resetPool();
```

> **Note**: The connection pool is static/global and shared across all handlers. Call `resetPool()` in your test teardown to ensure isolation between tests.

Debugging &amp; Profiling
-------------------------

[](#debugging--profiling)

Enable debug snapshots and optional profiling:

```
$handler = fetch_client()->getHandler();

// Enable debug with default options (captures everything)
$handler->withDebug();

// Or enable with specific options
$handler->withDebug([
    'request_headers' => true,
    'request_body' => true,
    'response_headers' => true,
    'response_body' => 1024,  // Truncate response body at 1024 bytes
    'timing' => true,
    'memory' => true,
    'dns_resolution' => true,
]);

// Enable profiling
$handler->withProfiler(new \Fetch\Support\FetchProfiler);

// Set log level (requires PSR-3 logger to be configured)
$handler->withLogLevel('info'); // default: debug

$response = $handler->get('https://api.example.com/users');

// Preferred: read per-response debug snapshot
$responseDebug = $response->getDebugInfo();

// Legacy fallback for BC: handler-level snapshot (may lag in concurrent flows)
$lastDebug = $handler->getLastDebugInfo();
```

Testing Support
---------------

[](#testing-support)

Fetch PHP includes built-in testing utilities for mocking HTTP responses:

```
use Fetch\Testing\MockServer;
use Fetch\Testing\MockResponse;

// Mock a single response
MockServer::fake([
    'GET https://api.example.com/users/1' => MockResponse::json([
        'id' => 1,
        'name' => 'Ada Lovelace',
    ]),
]);

$response = fetch('https://api.example.com/users/1');
// Returns mocked response without making an actual HTTP request
MockServer::assertSent('GET https://api.example.com/users/1');

// Mock a sequence of responses
MockServer::fake([
    'https://api.example.com/users/*' => MockResponse::sequence([
        MockResponse::json(['id' => 1]),
        MockResponse::json(['id' => 2]),
        MockResponse::notFound(),
    ]),
]);

fetch('https://api.example.com/users/alpha'); // gets id 1
fetch('https://api.example.com/users/beta');  // gets id 2
fetch('https://api.example.com/users/omega'); // 404 from sequence
```

Advanced Response Features
--------------------------

[](#advanced-response-features)

### Response Status Checks

[](#response-status-checks)

```
$response = fetch('https://api.example.com/data');

// Status category checks
$response->isInformational(); // 1xx
$response->isSuccess();       // 2xx
$response->isRedirection();   // 3xx
$response->isClientError();   // 4xx
$response->isServerError();   // 5xx

// Specific status checks
$response->isOk();            // 200
$response->isCreated();       // 201
$response->isNoContent();     // 204
$response->isNotFound();      // 404
$response->isForbidden();     // 403
$response->isUnauthorized();  // 401

// Generic status check
$response->isStatus(Status::CREATED);
$response->isStatus(201);
```

### Response Helpers

[](#response-helpers)

```
// Check if response contains JSON
if ($response->isJson()) {
    $data = $response->json();
}

// Get response as different types with error handling
$data = $response->json(assoc: true, throwOnError: false);
$object = $response->object(throwOnError: false);
$array = $response->array(throwOnError: false);
```

Connection Pool Management
--------------------------

[](#connection-pool-management)

Clean up connections or reset the pool (useful in tests):

```
$handler = fetch_client()->getHandler();

// Close all active connections
$handler->closeAllConnections();

// Reset the entire pool and DNS cache (useful in tests)
$handler->resetPool();

// Get pool statistics
$stats = $handler->getPoolStats();
// Returns: connections_created, connections_reused, total_requests, average_latency, reuse_rate
```

Async Notes
-----------

[](#async-notes)

- Async requests use the same pipeline (mocking, profiling, logging) but bypass caching by design.
- Matrix helpers (`async`, `await`, `all`, `race`, `map`, `batch`, `retry`) are re-exported in `Fetch\Support\helpers.php`.
- Errors are wrapped with method/URL context while preserving the original exception chain.
- Use `$handler->async()` to enable async mode, or use the Matrix async utilities directly.

License
-------

[](#license)

This project is licensed under the **MIT License** – see the [LICENSE](LICENSE) file for full terms.

The MIT License allows you to:

- Use the software for any purpose, including commercial applications
- Modify and distribute the software
- Include it in proprietary software
- Use it without warranty or liability concerns

This permissive license encourages adoption while maintaining attribution requirements.

Contributing
------------

[](#contributing)

Contributions are welcome! We're currently looking for help with:

- Expanding test coverage
- Improving documentation
- Adding support for additional HTTP features

To contribute:

1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/amazing-feature`)
3. Commit your Changes (`git commit -m 'Add some amazing-feature'`)
4. Push to the Branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

Acknowledgments
---------------

[](#acknowledgments)

- Thanks to **Guzzle HTTP** for providing the underlying HTTP client
- Thanks to all contributors who have helped improve this package
- Special thanks to the PHP community for their support and feedback

###  Health Score

56

—

FairBetter than 98% of packages

Maintenance77

Regular maintenance activity

Popularity45

Moderate usage in the ecosystem

Community23

Small or concentrated contributor base

Maturity65

Established project with proven stability

 Bus Factor1

Top contributor holds 88.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~23 days

Recently: every ~55 days

Total

21

Last Release

145d ago

Major Versions

1.2.0 → 2.0.02024-09-30

2.0.6 → 3.0.02025-05-03

PHP version history (4 changes)v1.x-devPHP ^8.2 || ^8.3

1.1.1PHP ^8.2

2.0.3PHP ^8.0

3.3.0PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/e6a36cdb0d28baadcd00b763ff51b42fdb5128186b1ff0e46c007d43376bfe4d?d=identicon)[Thavarshan](/maintainers/Thavarshan)

---

Top Contributors

[![Thavarshan](https://avatars.githubusercontent.com/u/10804999?v=4)](https://github.com/Thavarshan "Thavarshan (197 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (16 commits)")[![patinthehat](https://avatars.githubusercontent.com/u/5508707?v=4)](https://github.com/patinthehat "patinthehat (4 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (3 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")[![tresbach](https://avatars.githubusercontent.com/u/774244?v=4)](https://github.com/tresbach "tresbach (1 commits)")

---

Tags

fetchfetch-apifetch-phpguzzleguzzle-php-libraryguzzlehttphttphttp-clientjavascript-fetchjavascript-fetch-apiphp

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/jerome-fetch-php/health.svg)

```
[![Health](https://phpackages.com/badges/jerome-fetch-php/health.svg)](https://phpackages.com/packages/jerome-fetch-php)
```

###  Alternatives

[google/auth

Google Auth Library for PHP

1.4k272.7M162](/packages/google-auth)[shopify/shopify-api

Shopify API Library for PHP

4634.8M16](/packages/shopify-shopify-api)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[aporat/store-receipt-validator

PHP receipt validator for Apple App Store and Amazon Appstore

6503.9M9](/packages/aporat-store-receipt-validator)[theodo-group/llphant

LLPhant is a library to help you build Generative AI applications.

1.5k311.5k5](/packages/theodo-group-llphant)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
