PHPackages                             eypsilon/curler - 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. eypsilon/curler

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

eypsilon/curler
===============

Many/Curler | Another one CURLs the dust

2.0.0(3w ago)420MITPHPPHP &gt;=8.0CI passing

Since Jul 26Pushed 3w ago1 watchersCompare

[ Source](https://github.com/eypsilon/Curler)[ Packagist](https://packagist.org/packages/eypsilon/curler)[ RSS](/packages/eypsilon-curler/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (5)DependenciesVersions (6)Used By (0)

MANY/CURLER | Another one CURLs the dust
========================================

[](#manycurler--another-one-curls-the-dust)

> A modern, type-safe HTTP client for PHP 8.1+ with chainable API, pipeline callbacks, and support for the latest HTTP standards.

[![PHP Version](https://camo.githubusercontent.com/83dd395020c37276225039739320f6c8e7e99963ab21ee3d09282cb48dad2a60/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e312532422d626c7565)](https://php.net)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](https://opensource.org/licenses/MIT)[![Packagist](https://camo.githubusercontent.com/3aaa84dc3e5bacaa08f09a17d6ff7caba957c991cde24083f00f8608e52bd2bd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7061636b61676973742d65797073696c6f6e2532466375726c65722d6f72616e6765)](https://packagist.org/packages/eypsilon/curler)

✨ Features
----------

[](#-features)

- 🚀 **Chainable API** - Clean, fluent interface for all HTTP operations
- 🔒 **Type-safe** - Full PHP 8.1+ type declarations
- 📦 **HTTP/2 Support** - Default protocol for better performance
- 🔄 **QUERY Method** - RFC 9204 support for safe, idempotent queries with body
- 🔗 **Pipeline Callbacks** - Chain multiple transformations with validation
- 🖼️ **Image to Data URI** - Automatic base64 conversion for images
- 🔁 **Retry Logic** - Configurable retries with backoff
- 📊 **Meta Information** - Duration, size, timestamps for each request
- 🎯 **Authentication** - Basic, Bearer, Digest, API Key
- 📝 **Request History** - Track all requests with timing
- ⚡ **PSR-18 Ready** - Compatible with modern PHP standards

📦 Installation
--------------

[](#-installation)

```
composer require eypsilon/curler
```

🚀 Quick Start
-------------

[](#-quick-start)

```
use Many\Http\Curler;

// Simple GET request
$response = (new Curler())->get('https://api.example.com/users');
echo $response->getBody();

// With authentication and JSON
$response = (new Curler())
    ->authBearer('your-token-here')
    ->withJson(['name' => 'John Doe'])
    ->post('https://api.example.com/users');

// With callbacks and validation
$response = (new Curler())
    ->validate(fn($data) => !empty($data), 'Data not empty')
    ->jsonDecode()
    ->through(fn($data) => array_merge($data, ['processed' => true]))
    ->jsonEncode(JSON_PRETTY_PRINT)
    ->get('https://api.example.com/data');
```

📚 Documentation
---------------

[](#-documentation)

### Configuration

[](#configuration)

Set global configuration before making requests:

```
Curler::setConfig([
    // Default URL prefix (auto-applied to relative URLs)
    'default_url' => 'https://api.example.com',

    // Default headers sent with every request
    'default_headers' => [
        'X-App-Name' => 'MyApp',
        'Accept' => 'application/json',
    ],

    // Default CURL options
    'default_options' => [
        CURLOPT_TIMEOUT => 30,
        CURLOPT_USERAGENT => 'MyApp/1.0',
    ],

    // Features
    'enable_exceptions' => true,  // Throw exceptions on errors
    'enable_meta' => true,        // Include meta data in response
    'enable_history' => true,     // Track request history

    // Image conversion (MIME types to convert to data URIs)
    'image_to_data' => ['image/jpeg', 'image/png', 'image/webp'],

    // Retry configuration
    'retry_attempts' => 3,
    'retry_delay_ms' => 100,
    'retry_on_status' => [429, 500, 502, 503, 504],

    // Date format for timestamps
    'date_format' => 'Y-m-d H:i:s.u',
]);
```

### Basic Requests

[](#basic-requests)

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

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

// POST with form data
$response = (new Curler())
    ->withForm(['name' => 'John', 'email' => 'john@example.com'])
    ->post('https://api.example.com/users');

// POST with JSON
$response = (new Curler())
    ->withJson(['name' => 'John', 'email' => 'john@example.com'])
    ->post('https://api.example.com/users');

// PUT, PATCH, DELETE
$response = (new Curler())
    ->withJson(['name' => 'Jane'])
    ->put('https://api.example.com/users/123');

$response = (new Curler())->delete('https://api.example.com/users/123');
```

### HTTP QUERY Method (RFC 9204)

[](#http-query-method-rfc-9204)

The QUERY method is perfect for complex queries that exceed URL length limits:

```
// Simple QUERY with JSON body
$response = (new Curler())->query(
    'https://api.example.com/search',
    ['query' => 'php', 'filters' => ['status' => 'active']]
);

// QUERY with GraphQL
$response = (new Curler())->queryGraphQL(
    'https://api.example.com/graphql',
    'query GetUser($id: ID!) { user(id: $id) { name email } }',
    ['id' => '123']
);

// QUERY with URL parameters + body
$response = (new Curler())->query(
    'https://api.example.com/search',
    ['query' => 'php'],
    ['page' => 1, 'limit' => 10]  // URL query params
);
```

### Authentication

[](#authentication)

```
// Basic Auth
$response = (new Curler())
    ->authBasic('username', 'password')
    ->get('https://api.example.com/protected');

// Bearer Token
$response = (new Curler())
    ->authBearer('your-token-here')
    ->get('https://api.example.com/protected');

// Digest Auth
$response = (new Curler())
    ->authDigest('username', 'password')
    ->get('https://api.example.com/protected');

// API Key (custom header)
$response = (new Curler())
    ->authApiKey('your-api-key', 'X-API-Key')
    ->get('https://api.example.com/protected');
```

### Headers &amp; Query Parameters

[](#headers--query-parameters)

```
// Set headers
$response = (new Curler())
    ->withHeader('X-Custom', 'value')
    ->withHeaders([
        'X-Header-1' => 'value1',
        'X-Header-2' => 'value2',
    ])
    ->get('https://api.example.com');

// Set query parameters (chainable)
$response = (new Curler())
    ->url('https://api.example.com/users')
    ->query(['page' => 1, 'limit' => 10])
    ->mergeQuery(['sort' => 'desc'])
    ->get();
```

### Response Pipeline (Callbacks)

[](#response-pipeline-callbacks)

Transform responses through a pipeline of callbacks with validation:

```
$response = (new Curler())
    // Validate first
    ->validate(fn($data) => !empty($data), 'Data not empty')
    ->validate(fn($data) => Curler::isJson($data), 'Must be JSON')

    // Transform
    ->jsonDecode()
    ->through(fn($data) => array_merge($data, ['processed_at' => date('c')]))
    ->through(fn($data) => array_filter($data))
    ->jsonEncode(JSON_PRETTY_PRINT)

    // Final validation
    ->validate(fn($data) => Curler::isJson($data), 'Final must be JSON')

    ->get('https://api.example.com/data');
```

### Image Conversion

[](#image-conversion)

Automatically convert images to data URIs:

```
Curler::setConfig([
    'image_to_data' => ['image/jpeg', 'image/png', 'image/webp'],
    'default_url' => 'https://upload.wikimedia.org/wikipedia/commons/thumb',
]);

$response = (new Curler())->get('/path/to/image.jpg');
// $response->getBody() contains: data:image/jpeg;base64,/9j/4AAQ...
```

### Error Handling

[](#error-handling)

```
use Many\Http\Curler;
use Many\Exception\AppCallbackException;

try {
    $response = (new Curler())
        ->withExceptions(true)
        ->withRetry(3, 200)  // Retry 3 times with 200ms delay
        ->timeout(30)
        ->get('https://api.example.com/unreliable');

    if ($response->isSuccess()) {
        echo $response->getBody();
    }

} catch (AppCallbackException $e) {
    echo "Pipeline error: " . $e->getMessage();
    echo "Stage: " . $e->getStage();
    echo "Context: " . print_r($e->getContext(), true);

} catch (RuntimeException $e) {
    echo "HTTP error: " . $e->getMessage();
}
```

### Response Object

[](#response-object)

```
$response = (new Curler())->get('https://api.example.com/data');

// Check status
if ($response->isSuccess()) {
    $data = $response->getJson();  // Auto-decode JSON
} elseif ($response->isClientError()) {
    // 4xx errors
} elseif ($response->isServerError()) {
    // 5xx errors
}

// Get response data
$body = $response->getBody();              // Raw body
$json = $response->getJson();              // Decoded JSON
$status = $response->getStatusCode();      // HTTP status
$type = $response->getContentType();       // Content-Type
$size = $response->getSize();              // Size in bytes

// Get meta information (if enabled)
$meta = $response->getMeta();
$duration = $response->getMeta('duration');
$timestamp = $response->getMeta('timestamp');
```

### Request History

[](#request-history)

```
Curler::setConfig(['enable_history' => true]);

// Make some requests...

// Get all requests
$history = Curler::getHistory();

foreach ($history as $request) {
    echo sprintf(
        "[%s] %s %s (%0.4fs)\n",
        $request['method'],
        $request['status'],
        $request['url'],
        $request['duration']
    );
}

// Get request count
$total = Curler::getRequestCount();

// Clear history
Curler::clearHistory();
```

### Utility Methods

[](#utility-methods)

```
// Check if string is valid JSON
if (Curler::isJson($data)) {
    // It's JSON!
}

// Format bytes to human-readable size
echo Curler::formatBytes(memory_get_usage());  // "1.23 MB"

// Get timestamp with microseconds
echo Curler::dateMicroSeconds();  // "2026-07-22 08:42:51.988100"

// Get difference between two timestamps
$diff = Curler::dateMicroDiff('2026-07-22 08:00:00', '2026-07-22 08:00:05');
echo $diff;  // "05.000000"
```

🔧 Advanced Examples
-------------------

[](#-advanced-examples)

### Custom CURL Options

[](#custom-curl-options)

```
$response = (new Curler())
    ->withOptions([
        CURLOPT_SSL_VERIFYPEER => false,  // NOT recommended for production
        CURLOPT_VERBOSE => true,
        CURLOPT_PROXY => 'proxy.example.com:8080',
    ])
    ->get('https://api.example.com');
```

### Retry with Custom Status Codes

[](#retry-with-custom-status-codes)

```
$response = (new Curler())
    ->withRetry(
        attempts: 5,
        delayMs: 200,
        onStatus: [429, 503, 504]  // Only retry on these statuses
    )
    ->get('https://rate-limited-api.example.com');
```

### Building Complex Queries

[](#building-complex-queries)

```
$response = (new Curler())
    ->url('https://api.example.com/search')
    ->query([
        'q' => 'php',
        'sort' => 'stars',
        'order' => 'desc'
    ])
    ->authBearer('token')
    ->withRetry(3)
    ->get();
```

### 1. The Responder (Server-Side)

[](#1-the-responder-server-side)

```
