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 ]);
| Method | Sends the params as |
|---|---|
CurlMethod::GET | Query string on the url. |
CurlMethod::POST | A form body — or JSON, see below. |
CurlMethod::PUT / Patch / Delete | The 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.