Skip to content

HttpClient

Ray Fung edited this page Feb 26, 2026 · 3 revisions

HttpClient

Razy's HttpClient provides a fluent, cURL-based HTTP client for making external API requests. It supports JSON/form/multipart encoding, authentication, retries, timeouts, and request/response hooks.


Table of Contents


Quick Start

use Razy\Http\HttpClient;

$response = HttpClient::create()
    ->baseUrl('https://api.example.com')
    ->withToken('my-api-key')
    ->asJson()
    ->get('/users');

$users = $response->json();

Creating a Client

// Static factory
$client = HttpClient::create();

// Pre-configured for an API
$api = HttpClient::create()
    ->baseUrl('https://api.example.com/v2')
    ->withToken('secret-token')
    ->asJson()
    ->timeout(10);

All configuration methods return $this for fluent chaining.


Configuration

Base URL

$client->baseUrl('https://api.example.com/v2');

// Requests use relative paths
$client->get('/users');     // ??https://api.example.com/v2/users
$client->get('/users/42');  // ??https://api.example.com/v2/users/42

Headers

// Set multiple headers
$client->withHeaders([
    'Accept'       => 'application/json',
    'X-Request-Id' => 'abc-123',
]);

// Set a single header
$client->withHeader('X-Custom', 'value');

Authentication

// Bearer token
$client->withToken('eyJhbGciOiJIUzI1NiIs...');
// ??Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

// Basic auth
$client->withBasicAuth('username', 'password');
// ??Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Timeouts

$client->timeout(30);          // Total request timeout (seconds)
$client->connectTimeout(5);    // Connection timeout (seconds)

SSL Verification

// Disable SSL verification (development only!)
$client->withoutVerifying();

Content Types

// JSON (Content-Type: application/json)
$client->asJson();

// Form URL-encoded (Content-Type: application/x-www-form-urlencoded)
$client->asForm();

// Multipart (Content-Type: multipart/form-data)
$client->asMultipart();

Query Parameters

// Append query parameters to all requests
$client->withQuery([
    'api_key' => 'abc123',
    'format'  => 'json',
]);

// Combined with path: /users?api_key=abc123&format=json
$client->get('/users');

User Agent

$client->userAgent('MyApp/1.0');

Retries

// Retry up to 3 times with 1000ms delay between attempts
$client->retry(times: 3, sleepMs: 1000);

Retries are triggered on connection failures and 5xx server errors.

cURL Options

// Set arbitrary cURL options
$client->withCurlOption(CURLOPT_FOLLOWLOCATION, true);
$client->withCurlOption(CURLOPT_MAXREDIRS, 5);

Making Requests

GET

$response = $client->get('/users');
$response = $client->get('/users', ['role' => 'admin']);  // query params

POST

// JSON body (if asJson() is set)
$response = $client->post('/users', [
    'name'  => 'Alice',
    'email' => 'alice@example.com',
]);

// Form body (if asForm() is set)
$response = $client->asForm()->post('/login', [
    'username' => 'alice',
    'password' => 'secret',
]);

// Multipart file upload
$response = $client->asMultipart()->post('/upload', [
    'file' => new \CURLFile('/path/to/photo.jpg', 'image/jpeg', 'photo.jpg'),
    'description' => 'Profile photo',
]);

PUT / PATCH / DELETE

$response = $client->put('/users/42', ['name' => 'Alice Updated']);
$response = $client->patch('/users/42', ['name' => 'Alice Patched']);
$response = $client->delete('/users/42');

HEAD / OPTIONS

$response = $client->head('/health');
$response = $client->options('/users');

Generic Send

$response = $client->send('GET', '/custom-endpoint', [
    'key' => 'value',
]);

Handling Responses

All HTTP methods return an HttpResponse object.

Status Checks

$response->status();         // 200

// Boolean helpers
$response->successful();     // 2xx
$response->ok();             // 200
$response->failed();         // 4xx or 5xx
$response->redirect();       // 3xx
$response->clientError();    // 4xx
$response->serverError();    // 5xx

Body & JSON

// Raw body
$html = $response->body();

// Decode JSON
$data = $response->json();
// ['users' => [['id' => 1, 'name' => 'Alice'], ...]]

// Dot-notation access into JSON
$name = $response->jsonGet('users.0.name');
// 'Alice'

Response Headers

// All headers
$headers = $response->headers();

// Single header
$type = $response->header('Content-Type');
// 'application/json; charset=utf-8'

// Check if header exists
$response->hasHeader('X-Rate-Limit');

// Content type shorthand
$response->contentType();
// 'application/json; charset=utf-8'

Error Handling

// Throw HttpException on failure (4xx/5xx)
$response->throw();

// Conditional throw
$response->throwIf($response->status() === 422);

// The exception wraps the response object
try {
    $response->throw();
} catch (\Razy\Http\HttpException $e) {
    echo $e->getCode();       // HTTP status code (e.g., 404)
    echo $e->getMessage();    // Error message
    $resp = $e->response;     // Original HttpResponse
    $body = $resp->json();    // Access response body
}

Serialisation

$array = $response->toArray();
// ['status' => 200, 'headers' => [...], 'body' => '...']

Hooks

Register callbacks for request/response lifecycle:

$client->beforeSending(function (array &$options) {
    // Modify cURL options before the request
    $options[CURLOPT_VERBOSE] = true;
    
    // Log outgoing request
    Log::debug('HTTP Request', $options);
});

$client->afterResponse(function (HttpResponse $response) {
    // Log response
    Log::debug('HTTP Response', [
        'status' => $response->status(),
        'body'   => substr($response->body(), 0, 500),
    ]);
    
    // Rate limit tracking
    if ($response->hasHeader('X-Rate-Limit-Remaining')) {
        RateTracker::update((int) $response->header('X-Rate-Limit-Remaining'));
    }
});

Full Example

REST API Client

use Razy\Http\HttpClient;

class GitHubClient
{
    private HttpClient $http;

    public function __construct(string $token)
    {
        $this->http = HttpClient::create()
            ->baseUrl('https://api.github.com')
            ->withToken($token)
            ->asJson()
            ->withHeaders([
                'Accept'               => 'application/vnd.github.v3+json',
                'X-GitHub-Api-Version' => '2022-11-28',
            ])
            ->timeout(15)
            ->retry(times: 2, sleepMs: 1000);
    }

    public function getUser(string $username): array
    {
        return $this->http
            ->get("/users/{$username}")
            ->throw()
            ->json();
    }

    public function listRepos(string $username, int $page = 1): array
    {
        return $this->http
            ->get("/users/{$username}/repos", [
                'page'     => $page,
                'per_page' => 30,
                'sort'     => 'updated',
            ])
            ->throw()
            ->json();
    }

    public function createIssue(string $owner, string $repo, string $title, string $body): array
    {
        return $this->http
            ->post("/repos/{$owner}/{$repo}/issues", [
                'title' => $title,
                'body'  => $body,
            ])
            ->throw()
            ->json();
    }
}

// Usage
$github = new GitHubClient('ghp_xxxxxxxxxxxx');
$user = $github->getUser('octocat');
$repos = $github->listRepos('octocat');

API Reference

HttpClient

Method Signature Returns
create (): static New instance
baseUrl (string $url): static Set base URL
withHeaders (array $headers): static Set multiple headers
withHeader (string $name, string $value): static Set single header
withToken (string $token): static Bearer auth
withBasicAuth (string $user, string $pass): static Basic auth
timeout (int $seconds): static Request timeout
connectTimeout (int $seconds): static Connect timeout
withoutVerifying (): static Disable SSL verify
asJson (): static JSON content type
asForm (): static Form content type
asMultipart (): static Multipart content type
withQuery (array $params): static Default query params
userAgent (string $ua): static User-Agent header
retry (int $times, int $sleepMs): static Retry config
withCurlOption (int $opt, mixed $val): static cURL option
beforeSending (Closure $cb): static Pre-request hook
afterResponse (Closure $cb): static Post-response hook
get (string $url, array $query = []): HttpResponse GET request
post (string $url, array $data = []): HttpResponse POST request
put (string $url, array $data = []): HttpResponse PUT request
patch (string $url, array $data = []): HttpResponse PATCH request
delete (string $url, array $data = []): HttpResponse DELETE request
head (string $url): HttpResponse HEAD request
options (string $url): HttpResponse OPTIONS request
send (string $method, string $url, array $data = []): HttpResponse Generic request

HttpResponse

Method Signature Returns
status (): int HTTP status code
successful (): bool 2xx?
ok (): bool 200?
failed (): bool 4xx or 5xx?
redirect (): bool 3xx?
clientError (): bool 4xx?
serverError (): bool 5xx?
body (): string Raw body
json (): mixed JSON decoded
jsonGet (string $dotPath): mixed Dot-notation access
headers (): array All headers
header (string $name): ?string Single header
hasHeader (string $name): bool Header exists?
contentType (): ?string Content-Type
throw (): static Throw on failure
throwIf (bool $condition): static Conditional throw
toArray (): array Serialise

HttpException

Property Type Description
response HttpResponse Original response
Code int HTTP status code

Extends \RuntimeException.

??Previous: Queue Database

Clone this wiki locally