PHP RFC: Object-oriented curl API v2
- Version: 0.1
- Date: 2025-06-24
- Author: Eric Norris, erictnorris@gmail.com
- Status: Draft
- Implementation: TBA
Introduction
As part of the Resource to object conversion project, PHP internals developers converted curl
resource types to objects. These objects do not have any methods, and do not currently provide an object-oriented API for developers to use.
There have been at least two prior attempts at suggesting an object-oriented API for curl, but they never made it to a voting phase:
Creating an object-oriented API for curl
still seems worth doing, however. We can leverage PHP features (e.g. namespace, enumerations, and asymmetric visibility) to provide a clean and safe interface for developers.
Proposal
New 'Curl' Namespace
We'll move all existing Curl*
classes to the Curl
namespace, e.g. CurlHandle
will become Curl\Handle
. We'll maintain non-namespaced aliases for backwards compatibility.
New 'Curl\Option\*' enumerations
Multiple developers suggested using enumerations in response to the prior object-oriented curl
RFC:
What about making the CURL options an enumeration?
- kalle
I was also going to suggest to use enums for the options, and have them be grouped by what value type they need.
- girgias
The suggestion to use an Enum (or several) here is a good one and would help a lot with that, so I'm +1 there.
- crell
Using enumerations for curl
options will improve usability, as it will both clearly demonstrate what type is expected for a given option, and will allow static analysis to enforce the correct usage before code hits production environments.
We will create enumerations per value type. For brevity, I won't list all of the enumerations:
Curl\Option\StringOpt
namespace Curl\Option; enum StringOpt: int { case AbstractUnixSocket = CURLOPT_ABSTRACT_UNIX_SOCKET; case AcceptEncoding = CURLOPT_ACCEPT_ENCODING; case AltSvc = CURLOPT_ALTSVC; // ... }
Curl\Option\BoolOpt
namespace Curl\Option; enum BoolOpt: int { case Append = CURLOPT_APPEND; case AutoReferer = CURLOPT_AUTOREFERER; case CertInfo = CURLOPT_CERTINFO; // ... }
Curl\Option\FileOpt
namespace Curl\Option; enum FileOpt: int { case File = CURLOPT_FILE; case InFile = CURLOPT_INFILE; // ... }
Other enumerations
Similar to the above, we'll introduce an enumeration for any constants that are in fact enumerations, e.g. CURLINFO_*
constants:
namespace Curl; enum Info: int { case AppConnectTime = CURLINFO_APPCONNECT_TIME; // ... }
...and CURLPAUSE_*
constants:
namespace Curl; enum Pause: int { case All = CURLPAUSE_ALL; // ... }
Again, for brevity, I will not list all of the implementations.
New Curl\Exception class
Like the previous RFC, this RFC proposes that we add a new exception class to represent curl
errors.
namespace Curl; class Exception extends \Exception { /** Equivalent to curl_error() or the non-existent curl_multi_error(). */ protected string $message; /** Equivalent to curl_errno() or curl_multi_errno(). */ protected int $code; }
Curl\Handle class implementation
The Curl\Handle
class is a relatively straightforward translation of the non-object-oriented APIs, albeit with a few differences:
- Since we now have option enumerations, we provide a matching
set
method per enumeration class. - Instead of returning
false
, errors are treated as exceptions. - Like the previous RFC, this proposal opts to default to enabling the
CURLOPT_RETURNTRANSFER
behavior, instead of writing the result toSTDOUT
.
namespace Curl; class Handle { /** Equivalent to curl_errno(). */ public private(set) int $errorNumber; /** Equivalent to curl_error(). */ public private(set) string $errorMessage; /** * Equivalent to curl_init(). */ public function __construct(?string $uri = null); /** * Equivalent to curl_exec(), although it throws exceptions on failure, thus * the return type is string instead of string|bool. * * @throws \Curl\Exception */ public function execute(): string; /** * Equivalent to curl_getinfo(). */ public function getInfo(?\Curl\Info $option): mixed; /** * Equivalent to curl_pause(). */ public function pause(\Curl\Pause $flag): mixed; /** * Equivalent to curl_reset(). */ public function reset(): void; /** * Equivalent to curl_setopt() for boolean values. * * @throws \Curl\Exception */ public function setOptionBool(\Curl\Option\BoolOpt $opt, bool $value): \Curl\Handle; /** * Equivalent to curl_setopt() for string values. * * @throws \Curl\Exception */ public function setOptionString(Curl\Option\StringOpt $opt, string $value): Curl\Handle; /** * ...additional setOptionFoo() methods omitted for brevity... */ /** * Equivalent to curl_escape(). * * @throws \Curl\Exception */ public function escapeUrl(string $string): string; /** * Equivalent to curl_unescape(). * * @throws \Curl\Exception */ public function unescapeUrl(string $string): string; /** * Equivalent to curl_upkeep(). * * @throws \Curl\Exception */ public function upkeep(): void; }
Curl\MultiHandle class implementation
The Curl\MultiHandle
class is again a relatively straightforward translation of the non-object-oriented APIs with the same differences as the Cur\Handle
translation.
A few additional notable differences:
curl_multi_exec
will not returnCURLM_CALL_MULTI_PERFORM
, and will instead emulate newercurl
libraries by internally callingcurl
until it no longer returnsCURLM_CALL_MULTI_PERFORM
.curl_multi_select
is calledpoll
.
namespace Curl; class MultiHandle { /** Equivalent to curl_multi_errno(). */ public private(set) int $errorNumber; /** Equivalent to curl_multi_error(), if it existed. */ public private(set) string $errorMessage; /** * Equivalent to curl_multi_init(). */ public function __construct(); /** * Equivalent to curl_multi_add_handle(). * * @throws \Curl\Exception */ public function addHandle(\Curl\Handle $handle): void; /** * Equivalent to curl_multi_remove_handle(). * * @throws \Curl\Exception */ public function removeHandle(\Curl\Handle $handle): void; /** * Equivalent to curl_multi_exec(). Note that it will never return CURLM_CALL_MULTI_PERFORM, and will * internally emulate newer curl libraries. Returns true if there are still active connections. * * @throws \Curl\Exception */ public function execute(): bool; /** * Equivalent to curl_multi_getcontent(). */ public function getContent(\Curl\Handle $handle): ?string; /** * Equivalent to curl_multi_info_read(). */ public function getMessages(int &$queued_messages = null): array|false; /** * Equivalent to curl_multi_select(). */ public function poll(float $timeout = 1.0): int /** * Equivalent to `curl_multi_setopt()` for boolean values. * * @throws \Curl\Exception */ public function setOptionBool(Curl\MultiOption\BoolOpt $opt, bool $value): \Curl\MultiHandle; /** * Equivalent to `curl_setopt()` for int values. * * @throws \Curl\Exception */ public function setOptionInt(\Curl\MultiOption\IntOpt $opt, int $value): \Curl\MultiHandle; /** * ...additional setOptionFoo() methods omitted for brevity... */ }
Examples
Curl\Handle
:
<?php $ch = new Curl\Handle("https://example.com") ->setOptInt(Curl\Option\IntOpt::ConnectTimeout, 30) ->setOptBool(Curl\Option\BoolOpt::FollowLocation, true); try { echo $ch->execute() . "\n"; } catch (\Curl\Exception $ex) { echo "curl error ($ex->code): $ex->message\n"; }
Curl\MultiHandle
:
<?php $ch = new Curl\Handle("https://example.com") ->setOptInt(Curl\Option\IntOpt::ConnectTimeout, 30) ->setOptBool(Curl\Option\BoolOpt::FollowLocation, true); $mh = new Curl\MultiHandle(); $mh->addHandle($ch); while ($mh->execute()) { echo "multi handle is still active...\n"; } echo $mh->getContent($ch) . "\n";
Backward Incompatible Changes
The Curl
namespace will no longer be safe to use, and the aforementioned class names will be reserved, for example:
Curl\Handle
Curl\MultiHandle
Curl\Exception
Curl\Option\BoolOpt
- ...
CurlHandle
and other existing classes will still work, as this RFC proposes to alias them to their new namespaced counterparts.
Proposed PHP Version(s)
Next PHP 8.x (currently 8.5, potentially 8.6).
RFC Impact
To the Ecosystem
None expected.
To Existing Extensions
curl
will gain new classes, and existing curl
classes will gain new methods.
To SAPIs
None expected.
Open Issues
- Should we organize the
curl
option enumerations by value type? Or have a single enumeration for allcurl_setopt
options and allcurl_multi_setopt
options?
Future Scope
N/A
Voting Choices
Patches and Tests
TBA, pending initial discussion.
References
- Previous RFC (PHP RFC: Add OOP methods to Curl objects) and discussion
- Previous discussion Discussion about new Curl URL API and ext/curl improvements