====== 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 =====