PHP Logosymfony/http-client

`symfony/http-client` is a powerful and flexible HTTP client library provided by the Symfony project. It is designed to be PSR-18 compliant, meaning it adheres to the standardized HTTP Client Interface, making it interchangeable with other PSR-18 compatible clients. Its primary purpose is to simplify making HTTP requests to external APIs, web services, or any other HTTP endpoint from within a PHP application.

Key features and capabilities include:
* Ease of Use: Provides a clean and intuitive API for sending various types of HTTP requests (GET, POST, PUT, DELETE, etc.).
* Asynchronous Requests: Supports sending multiple requests concurrently without blocking, significantly improving performance for applications making many external calls.
* Streaming Responses: Allows processing large responses chunk by chunk, reducing memory consumption.
* Retry Mechanism: Built-in support for automatically retrying failed requests with configurable policies.
* Mocking for Testing: Offers robust tools for mocking HTTP responses, making unit and integration testing of HTTP client usage straightforward and reliable.
* Error Handling: Provides clear exceptions and status codes for handling network issues, timeouts, and application-level errors (e.g., 4xx, 5xx responses). By default, calling `getContent()` on a response with a 4xx or 5xx status code will throw an exception.
* Request Options: Extensive options for configuring requests, including headers, query parameters, request bodies (form, JSON, multipart), timeouts, authentication, proxies, and more.
* Adapter Agnosticism: It can use various underlying HTTP client implementations (like cURL, PHP streams, or even custom adapters) depending on availability and configuration, providing flexibility and robustness.
* Integration: While a core component of the Symfony framework, it can be used as a standalone library in any PHP project.

In essence, `symfony/http-client` provides a modern, robust, and developer-friendly way to interact with HTTP-based services, handling many common challenges like network issues, retries, and asynchronous operations out of the box.

Example Code

```php
<?php

require 'vendor/autoload.php';

use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;

// Install via Composer:
// composer require symfony/http-client

// 1. Create an HTTP client instance
// For advanced configurations (e.g., base URI, default headers), use options:
// $client = HttpClient::create(['base_uri' => 'https://jsonplaceholder.typicode.com/', 'headers' => ['Accept' => 'application/json']]);
$client = HttpClient::create();

echo "--- Basic GET Request ---\n";
try {
    // Make a GET request to a public API
    $response = $client->request('GET', 'https://jsonplaceholder.typicode.com/posts/1');

    // Get the HTTP status code (e.g., 200)
    $statusCode = $response->getStatusCode(); 
    // Get the content type (e.g., application/json). getHeaders() returns an associative array of arrays.
    $contentType = $response->getHeaders()['content-type'][0]; 
    // Get the response content as a string. This method throws exceptions for 4xx/5xx responses by default.
    $content = $response->getContent(); 

    echo "Status Code: {$statusCode}\n";
    echo "Content Type: {$contentType}\n";
    echo "Content (first 100 chars): " . substr($content, 0, 100) . "...\n\n";

    // Decode JSON content
    $data = json_decode($content, true);
    echo "Decoded Title: {$data['title']}\n\n";

} catch (TransportExceptionInterface $e) {
    // Catches network-related errors (e.g., host not found, connection refused)
    echo "Transport Error: {$e->getMessage()}\n\n";
} catch (ClientExceptionInterface $e) {
    // Catches 4xx HTTP client errors (e.g., 400 Bad Request, 404 Not Found)
    echo "Client Error (4xx): {$e->getMessage()}\n\n";
} catch (RedirectionExceptionInterface $e) {
    // Catches 3xx HTTP redirection errors (less common to catch explicitly)
    echo "Redirection Error (3xx): {$e->getMessage()}\n\n";
} catch (ServerExceptionInterface $e) {
    // Catches 5xx HTTP server errors (e.g., 500 Internal Server Error)
    echo "Server Error (5xx): {$e->getMessage()}\n\n";
} catch (\Exception $e) {
    // Catches any other unexpected exceptions
    echo "An unexpected error occurred: {$e->getMessage()}\n\n";
}


echo "--- POST Request with JSON Body ---\n";
try {
    $postData = [
        'title' => 'foo',
        'body' => 'bar',
        'userId' => 1,
    ];

    // Make a POST request. The 'json' option automatically sets Content-Type to application/json.
    $response = $client->request('POST', 'https://jsonplaceholder.typicode.com/posts', [
        'json' => $postData, 
    ]);

    $statusCode = $response->getStatusCode();
    $content = $response->getContent();

    echo "Status Code: {$statusCode}\n";
    echo "Response Content: {$content}\n\n";

    $responseData = json_decode($content, true);
    echo "New Post ID: {$responseData['id']}\n\n";

} catch (TransportExceptionInterface $e) {
    echo "Transport Error: {$e->getMessage()}\n\n";
} catch (ClientExceptionInterface $e) {
    echo "Client Error (4xx): {$e->getMessage()}\n\n";
} catch (RedirectionExceptionInterface $e) {
    echo "Redirection Error (3xx): {$e->getMessage()}\n\n";
} catch (ServerExceptionInterface $e) {
    echo "Server Error (5xx): {$e->getMessage()}\n\n";
} catch (\Exception $e) {
    echo "An unexpected error occurred: {$e->getMessage()}\n\n";
}

echo "--- GET Request with Error Handling (e.g., 404 Not Found) ---\n";
try {
    // This endpoint typically returns 404 Not Found
    $response = $client->request('GET', 'https://jsonplaceholder.typicode.com/posts/99999999999');

    // When `getContent()` is called on a response with a 4xx or 5xx status code,
    // it will throw a ClientExceptionInterface or ServerExceptionInterface.
    // To avoid throwing and inspect the content manually, you can pass `false` to getContent().
    if ($response->getStatusCode() >= 400) {
        echo "Received status code: " . $response->getStatusCode() . "\n";
        // getContent(false) prevents throwing an exception and returns the raw body
        echo "Error message (if any): " . $response->getContent(false) . "\n\n"; 
    } else {
        echo "Content: " . $response->getContent() . "\n";
    }

} catch (ClientExceptionInterface $e) {
    // This specific exception is caught when getContent() is called on a 4xx response
    echo "Client Error (4xx) caught for 404 example: {$e->getMessage()}\n";
    echo "Response status code: {$e->getResponse()->getStatusCode()}\n";
    echo "Response content: {$e->getResponse()->getContent(false)}\n\n";
} catch (TransportExceptionInterface $e) {
    echo "Transport Error: {$e->getMessage()}\n\n";
} catch (RedirectionExceptionInterface $e) {
    echo "Redirection Error (3xx): {$e->getMessage()}\n\n";
} catch (ServerExceptionInterface $e) {
    echo "Server Error (5xx): {$e->getMessage()}\n\n";
} catch (\Exception $e) {
    echo "An unexpected error occurred: {$e->getMessage()}\n\n";
}

?>
```