PHP Classes

Curler PHP HTTP Client Library: Send HTTP requests setup using a fluent interface

Recommend this page to a friend!
  Info   Documentation   Demos   View files Files   Install with Composer Install with Composer   Download Download   Reputation   Support forum   Blog    
Last Updated Ratings Unique User Downloads Download Rankings
2026-07-22 (Yesterday) RSS 2.0 feedNot enough user ratingsTotal: 75 All time: 10,198 This week: 64Up
Version License PHP version Categories
eypsilon-curler 2.0.0MIT/X Consortium ...8.1HTTP, Tools, PHP 7
Description 

Author

This package can send HTTP requests setup using a fluent interface.

It provides a set of functions that applications can chain to call an object of this class to configure several aspects of an HTTP request that the class will send using the PHP Curl extension.

Currently, it can:

- Set the URL of an HTTP request

- Set the parameters of an HTTP POST request

- Set the user name and password for requests that require HTTP Basic authentication

- Define what the class returns as the response to the HTTP request

- Etc.

The class can also set many HTTP request options using a single function call.

It can also execute the HTTP request and return the request-response when the server returns it.

Picture of Engin Ypsilon
  Performance   Level  
Name: Engin Ypsilon <contact>
Classes: 8 packages by
Country: Germany Germany
Age: 46
All time rank: 4106221 in Germany Germany
Week rank: 195 Up8 in Germany Germany Up
Innovation award
Innovation award
Nominee: 3x

Winner: 1x

Documentation

MANY/CURLER | 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 License Packagist

? 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

composer require eypsilon/curler

? 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

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

// 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)

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

// 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 & 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)

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

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

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 = (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

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

// 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

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

$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

$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)

<?php
/
 * responder.php - Empfängt und verarbeitet Requests
 * 
 * Dieser Script simuliert eine .htaccess geschützte API-Endpoint.
 * Es zeigt die empfangenen Daten und gibt sie als JSON oder print_r zurück.
 */

use Many\Http\Curler;

// Sammle alle Request-Informationen
$response = [
    'auth_type' => $_SERVER['AUTH_TYPE'] ?? 'error',
    'headers' => getallheaders(),
    'body' => file_get_contents('php://input'),
    'content_types' => [
        'json' => 'application/json',
        'form' => 'application/x-www-form-urlencoded',
        'text' => 'text/plain',
    ]
];

// Bestimme den Content-Type basierend auf Query-Parameter
$requestedType = $_GET['c_type'] ?? null;
$charset = $_GET['charset'] ?? 'UTF-8';
$contentType = $response['content_types'][$requestedType] ?? $_SERVER['CONTENT_TYPE'] ?? 'text/html';

// Setze den Content-Type Header
if ($requestedType && isset($response['content_types'][$requestedType])) {
    header(sprintf('Content-Type: %s; charset=%s', $contentType, $charset));
}

// Parse den Body wenn möglich
if ($requestedType === 'form' && ctype_print($response['body'])) {
    parse_str($response['body'], $response['body_parsed']);
}

// Gib die Response aus
exit($contentType === 'application/json' 
    ? json_encode($response, JSON_PRETTY_PRINT) 
    : print_r($response, true)
);

2. The Receiver (Client-Side)

<?php
/
 * receiver.php - Sendet Requests an den Responder
 * 
 * Zeigt wie Curler authentifizierte Requests an den geschützten Endpoint sendet.
 */

use Many\Http\Curler;

try {
    // Moderne Curler Syntax
    $response = (new Curler())
        ->authBasic('many', '123456')  // Oder authDigest() für Digest Auth
        ->withForm([
            'lorem_ipsum' => 'Dolor Sit Amet',
            'timestamp' => Curler::dateMicroSeconds(),
        ])
        ->jsonDecode()
        ->post('https://example.com/restricted/?c_type=json&charset=UTF-8');
    
    // Ausgabe der Response
    printf(
        '<pre>Status: %d%s%s</pre>',
        $response->getStatusCode(),
        PHP_EOL,
        $response->isSuccess() 
            ? json_encode($response->getJson(), JSON_PRETTY_PRINT) 
            : 'Error: ' . $response->getBody()
    );
    
} catch (Exception $e) {
    echo 'Request failed: ' . $e->getMessage();
}

3. Complete Example with Both Sides

<?php
/
 * Vollständiges Beispiel: Receiver + Responder
 * 
 * Zeigt einen kompletten Request/Response-Cycle mit
 * Authentifizierung, Datenübertragung und Verarbeitung.
 */

use Many\Http\Curler;

// 1. Konfiguration für den Responder (Server)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['responder'])) {
    $response = [
        'status' => 'ok',
        'message' => 'Data received successfully',
        'received_data' => $_POST,
        'headers' => getallheaders(),
        'auth_type' => $_SERVER['AUTH_TYPE'] ?? 'none',
        'timestamp' => Curler::dateMicroSeconds(),
    ];
    
    // Authentifizierung prüfen
    $auth = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    if (!str_contains($auth, 'Basic bWFueToxMjM0NTY=')) {
        http_response_code(401);
        $response['status'] = 'error';
        $response['message'] = 'Unauthorized';
    }
    
    header('Content-Type: application/json');
    exit(json_encode($response, JSON_PRETTY_PRINT));
}

// 2. Receiver (Client) - Sendet den Request
if (isset($_GET['receiver'])) {
    try {
        $response = (new Curler())
            ->authBasic('many', '123456')
            ->withForm([
                'name' => 'John Doe',
                'email' => 'john@example.com',
                'message' => 'Hello from Curler!'
            ])
            ->withQuery(['responder' => 1])
            ->jsonDecode()
            ->post('https://example.com/restricted/');
        
        if ($response->isSuccess()) {
            $data = $response->getJson();
            echo "? Success: " . ($data['message'] ?? 'OK') . "\n";
            echo "? Data: " . print_r($data['received_data'] ?? [], true);
        } else {
            echo "? Error: HTTP " . $response->getStatusCode() . "\n";
            echo $response->getBody();
        }
        
    } catch (Exception $e) {
        echo "? Request failed: " . $e->getMessage();
    }
    exit;
}

// 3. Demo HTML Interface
?>
<!DOCTYPE html>
<html>
<head>
    <title>Curler Receiver/Responder Demo</title>
    <style>
        body { font-family: sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
        pre { background: #f5f5f5; padding: 1rem; border-radius: 4px; overflow: auto; }
        .buttons { display: flex; gap: 1rem; margin: 1rem 0; }
        button { padding: 0.5rem 1rem; cursor: pointer; }
    </style>
</head>
<body>
    <h1>? Curler Receiver/Responder Demo</h1>
    <p>Dieses Beispiel zeigt, wie Curler sowohl als Client (Receiver) 
       als auch als Server (Responder) fungiert.</p>
    
    <div class="buttons">
        <a href="?receiver=1"><button>? Send Request (Receiver)</button></a>
        <a href="?responder=1"><button>? View Responder</button></a>
    </div>
    
    <?php
    // 4. Live Request mit Curler
    try {
        $response = (new Curler())
            ->authBasic('many', '123456')
            ->withForm([
                'lorem_ipsum' => 'Dolor Sit Amet',
                'source' => 'Curler Demo'
            ])
            ->withQuery(['responder' => 1])
            ->jsonDecode()
            ->post('https://example.com/restricted/');
        
        echo "<h2>? Live Response</h2>";
        echo "<pre>" . json_encode($response->getJson(), JSON_PRETTY_PRINT) . "</pre>";
        
    } catch (Exception $e) {
        echo "<h2>? Error</h2>";
        echo "<pre>" . htmlspecialchars($e->getMessage()) . "</pre>";
    }
    ?>
</body>
</html>

4. With .htaccess Protection (Apache)

# .htaccess - Schützt den Endpoint
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /path/to/.htpasswd
Require valid-user

5. Or with PHP Built-in Auth

<?php
// responder.php - Mit PHP-eigener Authentifizierung
$validUsers = [
    'many' => '123456',
    'admin' => 'admin123'
];

// Prüfe Basic Auth
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="Restricted"');
    http_response_code(401);
    exit('Authentication required');
}

$user = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];

if (!isset($validUsers[$user]) || $validUsers[$user] !== $pass) {
    http_response_code(401);
    exit('Invalid credentials');
}

// Auth erfolgreich - verarbeite Request
$data = json_decode(file_get_contents('php://input'), true) ?: $_POST;

$response = [
    'status' => 'ok',
    'user' => $user,
    'received' => $data,
    'timestamp' => Curler::dateMicroSeconds(),
];

header('Content-Type: application/json');
echo json_encode($response, JSON_PRETTY_PRINT);

? Migration from v1.x

| Old | New | |-----|-----| | ->callback('function') | ->through('function') | | ->callbackIf(['is_string'], 'trim') | ->validate(fn($d) => is_string($d))->through('trim') | | ->htmlChars() | ->through(fn($d) => htmlspecialchars($d)) | | $result['response'] | $response->getBody() | | $result['http_code'] | $response->getStatusCode() | | $result['meta'] | $response->getMeta() | | getCurlCount() | getRequestCount() | | getCurlTrace() | getHistory() | | readableBytes() | formatBytes() | | postFields() | withForm() or withJson() | | setOpt() | withOption() | | setOptArray() | withOptions() |

? Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

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

? License

This project is licensed under the MIT License - see the LICENSE file for details.

Vercel Demo GitHub

Author

Engin Ypsilon


  Files folder image Files (21)  
File Role Description
Files folder image.github (1 directory)
Files folder image.vscode (1 file)
Files folder imageapi (1 file)
Files folder imagepublic (4 files)
Files folder imagesrc (3 directories)
Files folder imagetests (2 files)
Accessible without login Plain text file composer.json Data Auxiliary data
Accessible without login Plain text file LICENSE Lic. License text
Accessible without login Plain text file README.md Doc. Documentation
Accessible without login Plain text file vercel.json Data Auxiliary data
Accessible without login Plain text file www.example.json Data Auxiliary data

  Files folder image Files (21)  /  .github  
File Role Description
Files folder imageworkflows (1 file)

  Files folder image Files (21)  /  .github  /  workflows  
File Role Description
  Accessible without login Plain text file php.yml Data Auxiliary data

  Files folder image Files (21)  /  .vscode  
File Role Description
  Accessible without login Plain text file settings.json Data Auxiliary data

  Files folder image Files (21)  /  api  
File Role Description
  Accessible without login Plain text file index.php Aux. Auxiliary script

  Files folder image Files (21)  /  public  
File Role Description
  Accessible without login Plain text file app.responder.receiver.php Example Example script
  Plain text file callback.functions.php Class Class source
  Plain text file index.php Class Class source
  Accessible without login Plain text file README.md Doc. Documentation

  Files folder image Files (21)  /  src  
File Role Description
Files folder imageException (3 files)
Files folder imageHttp (3 files)
Files folder imageTraits (1 file)

  Files folder image Files (21)  /  src  /  Exception  
File Role Description
  Plain text file AppCallbackException.php Class Class source
  Plain text file CurlException.php Class Class source
  Plain text file PipelineException.php Class Class source

  Files folder image Files (21)  /  src  /  Http  
File Role Description
  Plain text file Curler.php Class Class source
  Plain text file Response.php Class Class source
  Plain text file ResponsePipeline.php Class Class source

  Files folder image Files (21)  /  src  /  Traits  
File Role Description
  Plain text file AppCallback.php Class Class source

  Files folder image Files (21)  /  tests  
File Role Description
  Plain text file callback.functions.php Class Class source
  Accessible without login Plain text file index.php Test Class source

The PHP Classes site has supported package installation using the Composer tool since 2013, as you may verify by reading this instructions page.
Install with Composer Install with Composer
 Version Control Unique User Downloads Download Rankings  
 85%
Total:75
This week:0
All time:10,198
This week:64Up