haspadar/carl

Immutable object-oriented curl wrapper for PHP


README

PHP Version CI codecov PHPStan Level Psalm Mutation testing badge CodeRabbit Pull Request Reviews

Immutable HTTP client for PHP built on cURL. Pure OOP, no null, no static.
Inspired by Elegant Objects and cactoos.

Features

  • Final, immutable classes with single responsibility
  • No null values anywhere
  • No static methods or state
  • Lazy evaluation of HTTP requests
  • Built-in fake clients and responses for testing
  • No external dependencies except PHP and cURL

Simple Request Example

$response = new CurlClient()->outcome(
    new GetRequest('https://httpbin.org/get')
)->response();

echo $response->body(); 

Parallel Requests with onSuccess and onFailure

$client = new CurlClient();

$requests = [
    new GetRequest('https://httpbin.org/status/200'),
    new GetRequest('https://httpbin.org/status/404'),
];

$outcomes = $client->outcomes($requests, new class implements Reaction {
    public function onSuccess(Request $request, Response $response): void
    {
        echo "Success: " . $response->body() . "\n";
    }
    public function onFailure(Request $request, string $error): void
    {
        echo "Failure: " . $error . "\n";
    }
});
+Note: CurlClient returns outcomes in completion order (not request order).

๐Ÿงช Testing with Fakes

Carl provides fake classes for isolated unit testing without real HTTP calls. You can replace the real client with FakeClient to drive predefined outcomes.

Examples of fake outcomes:

  • AlwaysSuccessful โ€” always returns success (HTTP 200)
  • AlwaysFails โ€” always returns a failure with a given error message
  • Cycle โ€” cycles through a list of outcomes in order
  • FakeStatus โ€” returns an outcome with HTTP status code derived from the URI path

Example usage:

new FakeClient(new Cycle([
    new AlwaysSuccessful(new SuccessResponse("OK")),
    new AlwaysFails("network error"),
]))->outcomes(
    [
        new GetRequest('https://example.com/a'),
        new GetRequest('https://example.com/b'),
    ], 
    new OnSuccessResponse(
        fn (Response $response) => print $response->body()
    )
);
// Sequence: OK, error, OK, error, ...

๐Ÿ’ค Lazy Evaluation

Carl objects are lightweight and perform no heavy work in constructors. Network I/O occurs only when you call outcome() or outcomes(). Response parsing/consumption (e.g., body()) is deferred until you access it. This keeps composition predictable and fast.

๐Ÿ“ฅ Installation

composer require haspadar/carl
$response = new CurlClient()->outcome(
    new GetRequest('https://httpbin.org/get')
)->response();

echo $response->body(); 

Requirements

  • PHP 8.4+
  • ext-curl (enabled by default in most PHP distributions)

๐Ÿ“„ License

MIT