Framework
Features

Curl

The HTTP client every provider is built on, and the one to reach for when you call an API the framework does not wrap. One method covers the usual cases; its arguments cover the awkward ones.

A request

Curl::execute() takes a method and a url. By default it sends the parameters the way that method expects and decodes a JSON answer, so the common call is one line:

use Framework\Provider\Curl;
use Framework\Provider\Type\CurlMethod;

$result = Curl::execute(CurlMethod::GET, $url);
$result = Curl::execute(CurlMethod::GET, $url, [ "page" => 2 ]);
MethodSends the params as
CurlMethod::GETQuery string on the url.
CurlMethod::POSTA form body — or JSON, see below.
CurlMethod::PUT / Patch / DeleteThe url by default; a body when you ask for one.

How the body is sent

This is the argument most APIs come down to. The same parameters go out three different ways:

// Form encoded — the default for a POST
Curl::execute(CurlMethod::POST, $url, $params);

// A JSON body, with the header set for you
Curl::execute(CurlMethod::POST, $url, $params, jsonBody: true);

// URL encoded in the body, for the APIs that want that
Curl::execute(CurlMethod::POST, $url, $params, urlBody: true);

The distinction matters on the other methods too: a Put or Patch puts its parameters on the url unless jsonBody or urlBody asks for a body:

// PATCH /users/7?name=Ada
Curl::execute(CurlMethod::PATCH, "$url/7", [ "name" => "Ada" ]);

// PATCH /users/7  with {"name":"Ada"} as the body
Curl::execute(CurlMethod::PATCH, "$url/7", [ "name" => "Ada" ], jsonBody: true);

isCustom: true hands the method through untouched, for a server that expects something the defaults get wrong.

Headers & authentication

Headers are a plain map. Bearer tokens, API keys and content negotiation all go here:

Curl::execute(
    method:  CurlMethod::POST,
    url:     $url,
    params:  $body,
    headers: [
        "Authorization" => "Bearer $token",
        "Accept"        => "application/json",
    ],
    jsonBody: true,
);

For basic auth pass userPass instead, as user:password:

Curl::execute(CurlMethod::GET, $url, userPass: "$user:$password");

Reading the response

By default the answer is treated as JSON and decoded into an array. When it is not — an XML API, a plain text endpoint — turn that off and take the raw body:

$data = Curl::execute(CurlMethod::GET, $url);                        // decoded array
$text = Curl::execute(CurlMethod::GET, $url, jsonResponse: false);   // the raw string

With withHeaders: true the result becomes a pair, so you can read a rate limit or a location header alongside the body:

$result = Curl::execute(CurlMethod::GET, $url, withHeaders: true);

$result["response"];   // the body, decoded as usual
$result["headers"];    // the response headers

Errors & timeouts

A failed request normally comes back empty, which is fine when you only care whether it worked. When you need to know why, returnError: true returns the curl error instead:

$result = Curl::execute(CurlMethod::GET, $url, returnError: true);

if (isset($result["error"])) {
    ErrorLog::add("Request failed: " . $result["error"]);
}

The error keeps the shape you asked for — an error key with jsonResponse, a plain string without it, and alongside headers when those were requested.

The timeout defaults to 100 seconds for the whole request, with a 10 second connect timeout, and a transfer slower than 512 bytes/s for two minutes is abandoned — so a hung endpoint cannot hold a request open forever:

// A slow report that is worth waiting for
Curl::execute(CurlMethod::GET, $url, timeout: 300);

// A health check that should fail fast
Curl::execute(CurlMethod::GET, $url, timeout: 5);

disableSSL: true skips certificate verification. It exists for a staging server with a self-signed certificate — never point it at anything public.

Downloading & uploading

Two helpers handle files, so a large body never has to sit in memory. read() streams a url straight to disk:

$ok = Curl::read($url, $filePath);
$ok = Curl::read($url, $filePath, [ "Authorization" => "Bearer $token" ]);

It follows redirects, which is what most download links need. write() sends the contents of a file as the body of a request:

$result = Curl::write($url, $fileContent, [
    "Content-Type" => "application/pdf",
]);

Debugging

Curl options are integer constants, so a dumped option array is unreadable. parseOptions() turns one back into names:

Curl::parseOptions($options);
// [ "CURLOPT_URL" => "…", "CURLOPT_POST" => true, "CURLOPT_TIMEOUT" => 100 ]

When a call misbehaves, the usual order is: ask for returnError to see the curl error, drop jsonResponse to look at the raw body, then add withHeaders to check the status the server really sent.

Where it is used

Every integration in Providers goes through this class — OpenAI and Ollama for completions, Google Maps for geocoding, MercadoPago for checkout, MailChimp for campaigns, and the email and notification transports for delivery. Reading one of them is the quickest way to see a complete example.