====== PHP RFC: Object-oriented curl API v2 ======
* Version: 0.2
* Date: 2025-06-24
* Author: Eric Norris, erictnorris@gmail.com
* Status: Draft
* Implementation: TBA
===== Introduction =====
As part of the [[https://github.com/php/php-tasks/issues/6|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:
* [[curl-oop|PHP RFC: Add OOP methods to Curl objects]]
* [[https://externals.io/message/117958|Discussion about new Curl URL API and ext/curl improvements]]
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?
- [[https://externals.io/message/122371#122408|kalle]]
I was also going to suggest to use enums for the options, and have them be grouped by what value type they need.
- [[https://externals.io/message/122371#122410|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.
- [[https://externals.io/message/122371#122413|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 to ''STDOUT''.
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 return ''CURLM_CALL_MULTI_PERFORM'', and will instead emulate newer ''curl'' libraries by internally calling ''curl'' until it no longer returns ''CURLM_CALL_MULTI_PERFORM''.
* ''curl_multi_select'' is called ''poll''.
* ''curl_multi_info_read'' is called ''getMessages''.
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'':
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'':
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 all ''curl_setopt'' options and all ''curl_multi_setopt'' options?
===== Future Scope =====
N/A
===== Voting Choices =====
* Yes
* No
===== Patches and Tests =====
TBA, pending initial discussion.
===== References =====
* Previous RFC ([[curl-oop|PHP RFC: Add OOP methods to Curl objects]]) and [[https://externals.io/message/122371|discussion]]
* Previous discussion [[https://externals.io/message/117958|Discussion about new Curl URL API and ext/curl improvements]]
===== Rejected Features =====
===== Changelog =====