[PHP-DEV] [RFC][DISCUSSION] Object-oriented curl API v2

Hello Internals,

I'd like to formally propose a restart of the original object-oriented
curl API RFC (PHP: rfc:curl-oop):

The prior RFC seemed to get positive feedback, with a small consensus
around wanting enum(s) for curl options. I've taken that feedback and
incorporated it into a new RFC, as I am not the original author, but I
am interested in making another potential improvement to the curl
extension.

In a nutshell, this version of the RFC:

- uses enumerations for curl options and other curl constants
- introduces a new Curl namespace

I have not yet created an implementation PR. I realize that is
somewhat discouraged, but I believe that this should be relatively
straightforward to implement (there's also the previous RFC's PR to
build on top of). The implementation of this RFC as it is now will
likely be tedious, however, so I'd like to get feedback on the
enumeration idea before committing to the work.

I've outlined one open question in the RFC, which relates to the above:

- Should we organize the curl option enumerations by value type? Or
have a single enumeration for all curl_setopt options and another for
all curl_multi_setopt options?

If others (including the original RFC author) are interested in
working with me on this, I'm more than open to that, so please let me
know.

Thanks,
Eric

On Thu, Jun 26, 2025, at 11:25 AM, Eric Norris wrote:

Hello Internals,

I'd like to formally propose a restart of the original object-oriented
curl API RFC (PHP: rfc:curl-oop):

PHP: rfc:curl_oop_v2

The prior RFC seemed to get positive feedback, with a small consensus
around wanting enum(s) for curl options. I've taken that feedback and
incorporated it into a new RFC, as I am not the original author, but I
am interested in making another potential improvement to the curl
extension.

In a nutshell, this version of the RFC:

- uses enumerations for curl options and other curl constants
- introduces a new Curl namespace

I have not yet created an implementation PR. I realize that is
somewhat discouraged, but I believe that this should be relatively
straightforward to implement (there's also the previous RFC's PR to
build on top of). The implementation of this RFC as it is now will
likely be tedious, however, so I'd like to get feedback on the
enumeration idea before committing to the work.

I've outlined one open question in the RFC, which relates to the above:

- Should we organize the curl option enumerations by value type? Or
have a single enumeration for all curl_setopt options and another for
all curl_multi_setopt options?

If others (including the original RFC author) are interested in
working with me on this, I'm more than open to that, so please let me
know.

Thanks,
Eric

I still support this effort.

1. I don't think the Curl\Option namespace is necessary. They can just be in the main Curl namespace.

2. Please don't name the exception "Exception". It needs some slightly more useful name, to avoid confusion in a file that also uses \Exception.

3. I realize `Handle` is the name from the procedural API, but it's not very self-descriptive. Without knowing Curl, it's non-obvious that it's a self-executing request object. Is there a better name we could find while we're at it?

4. Love the use of aviz. :slight_smile:

5. Now here's the big one: Using enums rather than a bunch of constants is a good change. However, I feel like it doesn't go far enough.

For instance,

$ch = new Curl\Handle("https://example.com")
    ->setOptionInt(Curl\Option\IntOpt::ConnectTimeout, 30)
    ->setOptionBool(Curl\Option\BoolOpt::FollowLocation, true);

Would be 10x easier to use were it:

$ch = new Curl\Handle("https://example.com")
    ->setConnectionTimeout(30)
    ->setFollowLocation((true);

Or for that matter, properties would now allow for those to even just be set directly on typed properties. (Though it wouldn't be chainable.)

I realize that would be considerably more work to define all those methods (I don't even know how many Curl has, as I rarely use it directly). But it would result in a vastly easier to use API than "set some random integer-based field(EnumName, 30)". The latter is highly non-obvious. Especially when getting into POST requests with different body formats (something the RFC should include examples of), IMO, good ergonomics is more important than looking like the old, horribly obtuse API.

As a worst case, perhaps the top-20 options or so could be converted to methods/properties, and the rest left to be ugly flags?

--Larry Garfield

On Thu, Jun 26, 2025 at 11:27 Eric Norris <eric.t.norris@gmail.com> wrote:

Hello Internals,

I’d like to formally propose a restart of the original object-oriented
curl API RFC (https://wiki.php.net/rfc/curl-oop):

https://wiki.php.net/rfc/curl_oop_v2

The prior RFC seemed to get positive feedback, with a small consensus
around wanting enum(s) for curl options. I’ve taken that feedback and
incorporated it into a new RFC, as I am not the original author, but I
am interested in making another potential improvement to the curl
extension.

In a nutshell, this version of the RFC:

  • uses enumerations for curl options and other curl constants
  • introduces a new Curl namespace

I have not yet created an implementation PR. I realize that is
somewhat discouraged, but I believe that this should be relatively
straightforward to implement (there’s also the previous RFC’s PR to
build on top of). The implementation of this RFC as it is now will
likely be tedious, however, so I’d like to get feedback on the
enumeration idea before committing to the work.

I’ve outlined one open question in the RFC, which relates to the above:

  • Should we organize the curl option enumerations by value type? Or
    have a single enumeration for all curl_setopt options and another for
    all curl_multi_setopt options?

If others (including the original RFC author) are interested in
working with me on this, I’m more than open to that, so please let me
know.

Thanks,
Eric

IMO, this sounds like something that would be great to start as a userland OOP wrapper for cURL, where it can be iterated on and the interface can be tested and changed much quicker. Then, maybe proceed to an external extension that can be migrated into core later, once its interface is stable.

cURL is massive, and there are a lot of moving parts. I wouldn’t expect to get the API correct on the first try. :slight_smile:

Cheers,
Ben

IMO, this sounds like something that would be great to start as a userland OOP wrapper for cURL, where it can be iterated on and the interface can be tested and changed much quicker. Then, maybe proceed to an external extension that can be migrated into core later, once its interface is stable.

cURL is massive, and there are a lot of moving parts. I wouldn’t expect to get the API correct on the first try. :slight_smile:

Cheers,
Ben

(Apologies for the double-post Ben, I'm a little rusty and forgot to reply-all)

I see, thanks for your feedback! To clarify, I am not aiming to
produce a high-level API for curl. I would expect that most users
would still reach for libraries like Guzzle, etc.

I know that in the prior discussion, Rowan Tommins had a vision for a
high-level API ([RFC] OOP API for cURL extension - Externals), but I
share your sentiment that this would be difficult to get right in
core. I'd prefer to aim for a (mostly) direct translation of the
low-level API, which I think still has benefits on its own.

Thanks Larry!

1. I don't think the Curl\Option namespace is necessary. They can just be in the main Curl namespace.

I don't feel strongly here, but I thought it would be nice to group
the enums (if we go the multiple enum route) for discoverability.
Thoughts?

2. Please don't name the exception "Exception". It needs some slightly more useful name, to avoid confusion in a file that also uses \Exception.

Fair point! I could use the previous RFC's HandleException class name,
barring your next point...

3. I realize `Handle` is the name from the procedural API, but it's not very self-descriptive. Without knowing Curl, it's non-obvious that it's a self-executing request object. Is there a better name we could find while we're at it?

I'm open to this, but I don't have a suggestion myself.

4. Love the use of aviz. :slight_smile:

Thanks :slight_smile: I was happy to be able to use it here.

5. Now here's the big one: Using enums rather than a bunch of constants is a good change. However, I feel like it doesn't go far enough.
...
As a worst case, perhaps the top-20 options or so could be converted to methods/properties, and the rest left to be ugly flags?

I think my hesitation here is related to Ben's sibling response: I
want to thread the needle between making small improvements to the
low-level API while translating them to object-oriented equivalents,
and making a high-level API. The latter, I think, would invite a lot
more discussion, and would be potentially hard to get right.

That said, maybe your suggestion does thread that needle; having a
method / property for the N most-used options is still relatively
low-level since it's not expressing an opinion on how they should be
used. I'm not sure.

Ben Ramsey ramsey@php.net hat am 26.06.2025 18:54 CEST geschrieben:

On Thu, Jun 26, 2025 at 11:27 Eric Norris <eric.t.norris@gmail.com> wrote:

Hello Internals,

I’d like to formally propose a restart of the original object-oriented
curl API RFC (https://wiki.php.net/rfc/curl-oop):

https://wiki.php.net/rfc/curl_oop_v2

The prior RFC seemed to get positive feedback, with a small consensus
around wanting enum(s) for curl options. I’ve taken that feedback and
incorporated it into a new RFC, as I am not the original author, but I
am interested in making another potential improvement to the curl
extension.

In a nutshell, this version of the RFC:

  • uses enumerations for curl options and other curl constants
  • introduces a new Curl namespace

I have not yet created an implementation PR. I realize that is
somewhat discouraged, but I believe that this should be relatively
straightforward to implement (there’s also the previous RFC’s PR to
build on top of). The implementation of this RFC as it is now will
likely be tedious, however, so I’d like to get feedback on the
enumeration idea before committing to the work.

I’ve outlined one open question in the RFC, which relates to the above:

  • Should we organize the curl option enumerations by value type? Or
    have a single enumeration for all curl_setopt options and another for
    all curl_multi_setopt options?

If others (including the original RFC author) are interested in
working with me on this, I’m more than open to that, so please let me
know.

Thanks,
Eric

IMO, this sounds like something that would be great to start as a userland OOP wrapper for cURL, where it can be iterated on and the interface can be tested and changed much quicker. Then, maybe proceed to an external extension that can be migrated into core later, once its interface is stable.

cURL is massive, and there are a lot of moving parts. I wouldn’t expect to get the API correct on the first try. :slight_smile:

Cheers,
Ben

see: GitHub - phpcurl/curlwrapper: The simplest OOP cURL wrapper for PHP

On 26/06/2025 17:53, Larry Garfield wrote:

Now here's the big one: Using enums rather than a bunch of constants is a good change. However, I feel like it doesn't go far enough.

100% this.

Writing "$ch->setOptionInt(Curl\Option\IntOpt::ConnectTimeout, 30);" instead of "curl_setopt(CURLOPT_CONNECTTIMEOUT, 30);" is mostly rearranging the punctuation deck-chairs on the ugly code Titanic.

Particularly when you can *also* write "$ch->setOptionInt(Curl\Option\IntOpt::ConnectTimeoutMs, 30_000);" with the same effect.

I realize that would be considerably more work to define all those methods (I don't even know how many Curl has, as I rarely use it directly).

I believe there are 272 "CURLOPT_" constants currently documented in the PHP manual: PHP: Predefined Constants - Manual

So, I totally agree that we need a "set raw option" method to support some of the more niche features.

But that long list is also exactly why we badly need helpers for common use cases - the manual page for curl_setopt has been at the top of the charts for number of user comments for years, because nobody wants to read the descriptions for two hundred options they'll never need.

On 26/06/2025 18:21, Eric Norris wrote:

I know that in the prior discussion, Rowan Tommins had a vision for a
high-level API ([RFC] OOP API for cURL extension - Externals)

I think calling it a "vision for a high-level API" is making it sound far more grandiose than what I suggested. What I suggested, and would still like to see, is a small number of additional methods, for setting really common options in a more user-friendly way.

Looking at my earlier message, my finger-in-the-air number was even smaller than Larry's - a set of 10 methods, each covering a handful of overlapping or closely related options.

A single "setHttpMethod" method would bring immediate value, instead of choosing between CURLOPT_HTTPGET, CURLOPT_POST, CURLOPT_PUT, CURLOPT_NOBODY (for HEAD) and CURLOPT_CUSTOMREQUEST.

Adding more helpers in later versions is entirely trivial, but we could set the precedent with a first batch on day one.

The only other "high-level" API I see discussed in the previous thread is splitting the execute() method, for exactly the same reason as splitting setOpt(): type safety.

public function executeAndReturn(): string
public function executeAndOutput(): void
public function executeToFile(Stream $fileHandle): void
public function executeWithCallback(callable $wrIteFunction): void

The CURLOPT_RETURNTRANSFER, CURLOPT_FILE, and CURLOPT_WRITEFUNCTION would then not exist in the option enum(s), because there would be no way to make use of them.

Unlike the helper methods, that's one we have to decide in advance - it would be a mess to have those *as well as* a universal "execute(): ?string".

--
Rowan Tommins
[IMSoP]

On 26 Jun 2025, at 18:07, Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:

On 26/06/2025 17:53, Larry Garfield wrote:

Now here's the big one: Using enums rather than a bunch of constants is a good change. However, I feel like it doesn't go far enough.

100% this.

Writing "$ch->setOptionInt(Curl\Option\IntOpt::ConnectTimeout, 30);" instead of "curl_setopt(CURLOPT_CONNECTTIMEOUT, 30);" is mostly rearranging the punctuation deck-chairs on the ugly code Titanic.

Particularly when you can *also* write "$ch->setOptionInt(Curl\Option\IntOpt::ConnectTimeoutMs, 30_000);" with the same effect.

I realize that would be considerably more work to define all those methods (I don't even know how many Curl has, as I rarely use it directly).

I believe there are 272 "CURLOPT_" constants currently documented in the PHP manual: PHP: Predefined Constants - Manual

So, I totally agree that we need a "set raw option" method to support some of the more niche features.

But that long list is also exactly why we badly need helpers for common use cases - the manual page for curl_setopt has been at the top of the charts for number of user comments for years, because nobody wants to read the descriptions for two hundred options they'll never need.

On 26/06/2025 18:21, Eric Norris wrote:
I know that in the prior discussion, Rowan Tommins had a vision for a
high-level API ([RFC] OOP API for cURL extension - Externals)

I think calling it a "vision for a high-level API" is making it sound far more grandiose than what I suggested. What I suggested, and would still like to see, is a small number of additional methods, for setting really common options in a more user-friendly way.

Looking at my earlier message, my finger-in-the-air number was even smaller than Larry's - a set of 10 methods, each covering a handful of overlapping or closely related options.

A single "setHttpMethod" method would bring immediate value, instead of choosing between CURLOPT_HTTPGET, CURLOPT_POST, CURLOPT_PUT, CURLOPT_NOBODY (for HEAD) and CURLOPT_CUSTOMREQUEST.

Adding more helpers in later versions is entirely trivial, but we could set the precedent with a first batch on day one.

The only other "high-level" API I see discussed in the previous thread is splitting the execute() method, for exactly the same reason as splitting setOpt(): type safety.

public function executeAndReturn(): string
public function executeAndOutput(): void
public function executeToFile(Stream $fileHandle): void
public function executeWithCallback(callable $wrIteFunction): void

The CURLOPT_RETURNTRANSFER, CURLOPT_FILE, and CURLOPT_WRITEFUNCTION would then not exist in the option enum(s), because there would be no way to make use of them.

Unlike the helper methods, that's one we have to decide in advance - it would be a mess to have those *as well as* a universal "execute(): ?string".

--
Rowan Tommins
[IMSoP]

I’m excited at just the discussion of a possibility for PHP to be able to perform basic HTTP requests in a friendly way. Last month I worked on a solution that involved an SQS Worker that do some work and then sends the output back to the origin via a HTTP request that is passed as part of the message; basically a callback over the wire. However, running it on Serverless as a plugin that makes no assumption about the project state, I couldn’t use Guzzle or any composer library for that matter. I had to stick with what’s available on Amazon Linux and Bref, which cURL is, but it’s such an unpleasant experience to have to write HTTP requests like it’s 2003.

I really hope some minor helpers can be added to this OOP version of cURL and I agree that it doesn’t need to be hashed out, perfect or even cover 270 cases. Just a simple GET/POST/PUT/PATCH/DELETE with a string body and some headers manipulation would go 99% of the way already.

Thanks Rowan!

I think calling it a "vision for a high-level API" is making it sound
far more grandiose than what I suggested. What I suggested, and would
still like to see, is a small number of additional methods, for setting
really common options in a more user-friendly way.

I apologize for misrepresenting your suggestion. I was mainly
responding to your "the eventual aim should be that users don't need a
userland wrapper" comment, which I worry sounds closer to what Ben was
(I believe) objecting to - a higher-level wrapper. Upon re-reading
though, I think that's potentially an unfair take - you did go on to
say "...just to make a simple request in a readable way".

Looking at my earlier message, my finger-in-the-air number was even
smaller than Larry's - a set of 10 methods, each covering a handful of
overlapping or closely related options.

A single "setHttpMethod" method would bring immediate value, instead of
choosing between CURLOPT_HTTPGET, CURLOPT_POST, CURLOPT_PUT,
CURLOPT_NOBODY (for HEAD) and CURLOPT_CUSTOMREQUEST.

Adding more helpers in later versions is entirely trivial, but we could
set the precedent with a first batch on day one.

I'm not opposed to this, but I am - as previously stated - nervous
about how and where we draw this line, since I expect there may be a
lot of opinions here. That said, if the general consensus is that we
want direct methods or properties for the N most common use-cases, I'm
happy to make that change to the RFC.

I can try to take a look at the curl options and do some github
searches to see if I can identify common patterns. I agree that
setting the HTTP method and timeout are good contenders. If someone
else wants to propose a list as well, feel free!

The only other "high-level" API I see discussed in the previous thread
is splitting the execute() method, for exactly the same reason as
splitting setOpt(): type safety.

public function executeAndReturn(): string
public function executeAndOutput(): void
public function executeToFile(Stream $fileHandle): void
public function executeWithCallback(callable $wrIteFunction): void

The CURLOPT_RETURNTRANSFER, CURLOPT_FILE, and CURLOPT_WRITEFUNCTION
would then not exist in the option enum(s), because there would be no
way to make use of them.

Unlike the helper methods, that's one we have to decide in advance - it
would be a mess to have those *as well as* a universal "execute(): ?string".

If we were to go this route, I would suggest:

public function fetch(): string
public function execute(resource|callable $out): void

that is, one method to just return the contents of the URL, and
another to either write output to a file (including STDOUT, if the
user desires), or to send output to a write callback.

On 26.06.2025 18:25, Eric Norris wrote:

- uses enumerations for curl options and other curl constants

Overall I think the RFC is a good opportunity to clean up a few legacy oddities in the curl API, but I need to throw in my 2c about enum usage here, as someone that has actually implemented some complexish curl-based client [1].

Currently the API is:

 curl\_setopt\($h, CURLOPT\_SMTH, 3\);

Now moving this into multiple enums to me brings one major issue, that I first have to think about which type the option has to then know on which enum I find it.

I understand this brings type safety on the value side if you have multiple setOptionInt etc, but that does not seem worth the trade-off to me. Type safety can already be done in static analyzers as they can see which option you pass in, they know which value should be allowed.

Then on top of that, assuming we'd put all options in one enum.. I am still wondering what the added value is compared to simply having the global constants (they could be namespaced if anything...). It's longer to type, and does not provide any better autocompletion. \Curl\Option::ConnectTimeout vs CURLOPT_CONNECT_TIMEOUT sounds ok to me if people feel strongly pro-enum, but I do really hope it is then a single enum + setOption() call.

Best,
Jordi

[1] composer/src/Composer/Util/Http/CurlDownloader.php at main · composer/composer · GitHub

Jordi Boggiano
@seldaek - https://seld.be

Reading this I can’t help but feel like there’s some cognitive bias because you have written a Curl class yourself. The author of a PHP Class that wraps Curl needs to know about every option and how to translate them back-and-forth, which is really the insight I take from your message. As a PHP developer making HTTP requests, I would never have guessed 270 options for Curl configurations and having them split into multiple Enums gives me smaller “boxes” that are easier to mentally parse individually. With that said, splitting enumerations by type rather than context does weaken the argument of split enums. I wouldn’t be instinctively looking for “what enum do I need that is an int?” or “what enum do I need that is a string?” unless I already know the implementation details by heart. The Info and Pause enumerations seem more useful in that regard as they reduce the scope in which I need to understand, process and decide.

With that said, for me this also threads into the bikeshedding area that could spiral into a failed RFC. Be it a single Enum for everything, constants, context-based enums or type-based enums, I would much rather have this RFC than not have it. PHP is one of the most important Web applications in the world and it severely lacks the ability to simplify Http. We could take inspiration from Python Requests [1] or Node Fetch [2] as a really simple and straightforward API that covers the vast majority of cases. I take the point that we don’t need to go all the way and invent a RequestClient library in PHP and this RFC is threading on the small step of converting Curl procedural API into OOP, which is where Larry and Rowans recommendations about some minor helper methods go a really really long way compared to where we are today.

[1] https://www.w3schools.com/PYTHON/ref_requests_post.asp
[2] https://nodejs.org/en/learn/getting-started/fetch#basic-post-usage

···

Marco Deleu

On Fri, Jun 27, 2025, at 8:01 AM, Deleu wrote:

On Fri, Jun 27, 2025 at 9:24 AM Jordi Boggiano <j.boggiano@seld.be> wrote:

On 26.06.2025 18:25, Eric Norris wrote:
> - uses enumerations for curl options and other curl constants

Overall I think the RFC is a good opportunity to clean up a few legacy
oddities in the curl API, but I need to throw in my 2c about enum usage
here, as someone that has actually implemented some complexish
curl-based client [1].

Currently the API is:

     curl_setopt($h, CURLOPT_SMTH, 3);

Now moving this into multiple enums to me brings one major issue, that I
first have to think about which type the option has to then know on
which enum I find it.

I understand this brings type safety on the value side if you have
multiple setOptionInt etc, but that does not seem worth the trade-off to
me. Type safety can already be done in static analyzers as they can see
which option you pass in, they know which value should be allowed.

Then on top of that, assuming we'd put all options in one enum.. I am
still wondering what the added value is compared to simply having the
global constants (they could be namespaced if anything...). It's longer
to type, and does not provide any better autocompletion.
\Curl\Option::ConnectTimeout vs CURLOPT_CONNECT_TIMEOUT sounds ok to me
if people feel strongly pro-enum, but I do really hope it is then a
single enum + setOption() call.

Best,
Jordi

[1]
composer/src/Composer/Util/Http/CurlDownloader.php at main · composer/composer · GitHub

Jordi Boggiano
@seldaek - https://seld.be

Reading this I can't help but feel like there's some cognitive bias
*because* you have written a Curl class yourself. The author of a PHP
Class that wraps Curl needs to know about every option and how to
translate them back-and-forth, which is really the insight I take from
your message. As a PHP developer making HTTP requests, I would never
have guessed 270 options for Curl configurations and having them split
into multiple Enums gives me smaller "boxes" that are easier to
mentally parse individually. With that said, splitting enumerations by
type rather than context does weaken the argument of split enums. I
wouldn't be instinctively looking for "what enum do I need that is an
int?" or "what enum do I need that is a string?" unless I already know
the implementation details by heart. The Info and Pause enumerations
seem more useful in that regard as they reduce the scope in which I
need to understand, process and decide.

I will agree here. Splitting enums by type doesn't add much value. Splitting them by topic might, if the topics are sufficiently distinct that a separate method makes sense. I don't know Curl's API well enough to make specific recommendations there.

--Larry Garfield

On Thu, Jun 26, 2025, at 10:51 PM, Eric Norris wrote:

Adding more helpers in later versions is entirely trivial, but we could
set the precedent with a first batch on day one.

I'm not opposed to this, but I am - as previously stated - nervous
about how and where we draw this line, since I expect there may be a
lot of opinions here. That said, if the general consensus is that we
want direct methods or properties for the N most common use-cases, I'm
happy to make that change to the RFC.

I can try to take a look at the curl options and do some github
searches to see if I can identify common patterns. I agree that
setting the HTTP method and timeout are good contenders. If someone
else wants to propose a list as well, feel free!

My gut sense is that the following use cases would cover the 80% case:

GET http://www.example.com/foo?bar=baz

(Send a GET request with a complex URL. Probably needs both transparent redirecting and not options.)

POST http://www.example.com/foo
with a JSON body

(This means setting a body string/array, and a content-type header.)

POST http://www.example.com/foo
with a multipart/form-data body

(This means making it easy to set the body, and corresponding content-type header.)

And those should also allow setting an Accept header. Which means probably any header should be easy to set, with maybe one or two extra-promoted ones. (Like, when setting the body you can also set the content type?)

I don't know if specifying an HTTP version is relevant/useful. I have never done so myself, but that proves little.

On the return side, I almost always check the status code, at least. That's probably the only non-body thing I check. :slight_smile: So making that trivial is also important. Whether that's via a mini-response object instead of a string or something else is open for discussion, I think.

Other questions:

* We have these shiny new URL/URI objects. Should those be supported directly?

--Larry Garfield

On 6/27/25 08:01, Deleu wrote:

With that said, for me this also threads into the bikeshedding area that could spiral into a failed RFC. Be it a single Enum for everything, constants, context-based enums or type-based enums, I would much rather have this RFC than not have it. PHP is one of the most important Web applications in the world and it severely lacks the ability to simplify Http. We could take inspiration from Python Requests [1] or Node Fetch [2] as a really simple and straightforward API that covers the vast majority of cases.

Python Requests and Node Fetch are simple and straightforward for users
*because* they're not simple and straightforward to develop. A lot of
care and detailed decisions have gone into those APIs to make them feel
simple and straightforward when used.

Python Requests[^1] is a massive third-party library that users install
separately from Python. It also depends on urllib3[^2], another massive
third-party library that serves as its underlying HTTP client.

Node Fetch uses the WHATWG Fetch Living Standard[^3], which, again, is
neither simple nor straightforward. It just feels that way when using it
because they did a damn good job defining it.

As I said earlier,[^4] this level of detail should live in userland.
Arguably, as in the case of Fetch, something like this *could* live in
core. There was an RFC over 10 years ago that proposed moving pecl_http
into core.[^5] However, if we decide to go that route, I think it needs
to be a much bigger undertaking, perhaps with a working group formed to
hash out the details and design the API. I think it's better to let userland handle something like this, so core can focus on optimizations
that reduce the "if it's in C, it'll be faster" arguments.

I take the point that we don't need to go all the way and invent a RequestClient library in PHP and this RFC is threading on the small step of converting Curl procedural API into OOP, which is where Larry and Rowans recommendations about some minor helper methods go a really really long way compared to where we are today.

I'm fully on board with making the curl extension API easier to use.

Let's clearly state the problem and use-case we're attempting to solve
for and focus on a solution targeting that and nothing more (without
limiting ourselves for future expansion, of course). Maybe after stating
the problem clearly, we'll find it easier to build consensus on a solution.

The RFC hints at type safety and converting the curl procedural API into
OOP as motivating factors. I don't think these clearly state the problem
we're trying to solve, so if they continue to be the motivating factors,
this discussion is bound to continue into bikeshedding territory.

Here are some of the more concrete things I've seen mentioned that hint
at the problem:

From Rowan:

But that long list is also exactly why we badly need helpers for common use cases - the manual page for curl_setopt has been at the top of the charts for number of user comments for years, because nobody wants to read the descriptions for two hundred options they'll never need.

From Marco:

perform basic HTTP requests in a friendly way

These are great starts. Solving for the documentation problem might not even require changes to the extension. We probably need to further define what is meant by "friendly," though. Otherwise, we'll find ourselves back at the bike shed.

So, what's the problem (from a user's perspective) this RFC solves?

Cheers,
Ben

[^1]: GitHub - psf/requests: A simple, yet elegant, HTTP library.
[^2]: GitHub - urllib3/urllib3: urllib3 is a user-friendly HTTP client library for Python
[^3]: https://fetch.spec.whatwg.org
[^4]: php.internals: Re: [RFC][DISCUSSION] Object-oriented curl API v2
[^5]: PHP: rfc:pecl_http

On 27/06/2025 04:51, Eric Norris wrote:

I can try to take a look at the curl options and do some github
searches to see if I can identify common patterns. I agree that
setting the HTTP method and timeout are good contenders. If someone
else wants to propose a list as well, feel free!

For what it's worth, I grepped my work code base for "CURLOPT_", and grouped the results into the categories below. Some of these are definitely more "advanced" than others, and a lot could just be one helper covering the whole category.

Really basic (already in the proposed constructor):
CURLOPT_URL

Setting HTTP method:
CURLOPT_HTTPGET
CURLOPT_POST
CURLOPT_CUSTOMREQUEST

Request body (despite name, handles both form data and raw string bodies):
CURLOPT_POSTFIELDS

Request headers:
CURLOPT_HTTPHEADER
CURLOPT_USERAGENT
CURLOPT_ENCODING (deprecated in favour of CURLOPT_ACCEPT_ENCODING)

Response body handling:
CURLOPT_FILE
CURLOPT_RETURNTRANSFER

Response header handling (extremely fiddly; it would be AMAZING to have something like getLastResponseHeaders() returning an array):
CURLOPT_HEADER
CURLOPT_HEADERFUNCTION

Auth:
CURLOPT_HTTPAUTH
CURLOPT_USERPWD
CURLOPT_SSLCERT (there's also a CURLOPT_SSLCERT_BLOB)
CURLOPT_SSLKEY

Error and redirect handling:
CURLOPT_FOLLOWLOCATION
CURLOPT_FAILONERROR

Timeouts:
CURLOPT_CONNECTTIMEOUT
CURLOPT_TIMEOUT
CURLOPT_TIMEOUT_MS

Cookie handling:
CURLOPT_COOKIE
CURLOPT_COOKIEFILE
CURLOPT_COOKIEJAR

Connection parameter tuning:
CURLOPT_HTTP_VERSION
CURLOPT_SSLVERSION
CURLOPT_SSL_VERIFYPEER (common, but shouldn't be!)

Debug output (like response headers, this is very unintuitive for PHP users; $ch->enableVerboseLogs() and $ch->getVerboseLogs() would be very handy):
CURLOPT_VERBOSE
CURLOPT_CAINFO
CURLOPT_STDERR

If we were to go this route, I would suggest:

public function fetch(): string
public function execute(resource|callable $out): void

that is, one method to just return the contents of the URL, and
another to either write output to a file (including STDOUT, if the
user desires), or to send output to a write callback.

That makes a lot of sense! I've never been very good at keeping names and sentences short :slight_smile:

--
Rowan Tommins
[IMSoP]

-----Original Message-----
From: Eric Norris <eric.t.norris@gmail.com>
Sent: Thursday, June 26, 2025 7:25 PM
To: PHP internals <internals@lists.php.net>
Subject: [PHP-DEV] [RFC][DISCUSSION] Object-oriented curl API v2

I'd like to formally propose a restart of the original object-oriented curl API RFC

Cool. Calling functions with object as an argument to modify it's state feels very C-ish and wildly different from how contemporary PHP is usually written. So even a 1:1 translation into a more familiar API is welcome.

It still feels a bit unfamiliar to me that part of the response (the body) is returned from `execute()` while the other stuff (like the status code) is stored in the state of the Handle instance. Is this statefulness suggested by libcurl and reflects it's object? Or is it a choice on the PHP side? I'm just wondering, this is probably not something that should be changed on the Handle class but maybe on some new layer.

The enums that you propose are backed. It seems appropriate in the sense of continuity, but is there a need for that? I'd argue that option names like AbstractUnixSocket or AutoReferer are values per se and there is no usable meaning for those integer values behind them. Maybe the translation to numeric constants (assuming it's necessary) can be done internally (in the Handle) via a `match` or `switch` statement and let's have pure enums?

I see that the option setters return ` \Curl\Handle`, but it is a bit unclear whether it will actually return `static`, `self` or `$this`. I assume the latter but it would be nice to document that we're mutating the same object.

Should we organize the curl option enumerations by value type? Or have a single enumeration for all curl_setopt options and another for all curl_multi_setopt options?

In fact I would expect a single `->setOption()` instead of the type specific ones. Having to specify the argument type in the setter name and/or the enum name doesn't sound very enjoyable to me.

BR,
Juris

Hi all,

On 26/06/2025 18:21, Eric Norris wrote:

I know that in the prior discussion, Rowan Tommins had a vision for a
high-level API ([RFC] OOP API for cURL extension - Externals)

I think calling it a "vision for a high-level API" is making it sound far more grandiose than what I suggested. What I suggested, and would still like to see, is a small number of additional methods, for setting really common options in a more user-friendly way.

To avoid drifting too much from existing expectations about how such a cURL interface *ought* to look (as vs what happened with the URL RFC) it would be wise to examine existing userland work earlier rather than later.

A brief search at Packagist reveals php-curl-class/php-curl-class - Packagist and curl/curl - Packagist right away, each with millions of downloads already.

These and other userland offerings should help inform the first steps, in any case.

-- pmj

Hello Internals,

I'd like to formally propose a restart of the original object-oriented
curl API RFC (PHP: rfc:curl-oop):

PHP: rfc:curl_oop_v2

The prior RFC seemed to get positive feedback, with a small consensus
around wanting enum(s) for curl options. I've taken that feedback and
incorporated it into a new RFC, as I am not the original author, but I
am interested in making another potential improvement to the curl
extension.

In a nutshell, this version of the RFC:

- uses enumerations for curl options and other curl constants
- introduces a new Curl namespace

I have not yet created an implementation PR. I realize that is
somewhat discouraged, but I believe that this should be relatively
straightforward to implement (there's also the previous RFC's PR to
build on top of). The implementation of this RFC as it is now will
likely be tedious, however, so I'd like to get feedback on the
enumeration idea before committing to the work.

I've outlined one open question in the RFC, which relates to the above:

- Should we organize the curl option enumerations by value type? Or
have a single enumeration for all curl_setopt options and another for
all curl_multi_setopt options?

If others (including the original RFC author) are interested in
working with me on this, I'm more than open to that, so please let me
know.

Thanks,
Eric

Thank you for taking the initiative to work on this. It's long
overdue, and the pattern we have now — that a `CurlHandle` object is
passed to functions — is not as intuitive as calling a method on that
object.

However, I softly oppose this RFC in its current state and the way it
seems to be going.

I have pushed Curl and libcurl to some uncommon cases such as HTTP/3,
DoH, the new debug callback (which I authored the PR for), IMAP, and a
few other obscure tweaks. For all of them, the current `curl_setopt`
worked, and the more I used it, the more I understood that having just
a few Curl functions and a sea of Curl options is the least
"presumptive way", regardless of the protocol and the options Curl
provides.

The extension is named `Curl`, because it's supposed to provide Curl
functionality into PHP. It provides low-level functionality, but we
should leave it to the PHP users to build the libraries that have
fluent APIs.

This way, we can have excellent purpose-built HTTP clients, IMAP
clients, Tor proxies, etc, all built using the low-level functionality
the extension offers.The HTTP client authors know the Curl options
really well to build a secure and fast HTTP client, and someone else
building an IMAP client can provide an intuitive API but still use the
same underlying Curl functionality. I understand that your RFC does
not propose a high-level API, and I agree with you that a high-level
API will need a lot of discussion.

There's a ton of Curl constants, and you are right that it could
really use some organizing. The upstream Curl project has not done it,
most likely because the options behave differently depending on the
protocol, and simply because it's a lot of work and BC trouble to do
so. I don’t think we can realistically have a meaningful discourse on
how to semantically group the constants into meaningful Enums. I'd
also argue that if libcurl is OK with having a sea of Curl options, we
should not be the one to group them.

Grouping them by the expected value type is one way to do it, but as
mentioned elsewhere in replies, now, the consumers of the API will
have to figure out whether that one Curl option they always used
belongs to `IntOpt` or a `BoolOpt`. It will help with
`CURLOPT_SSL_VERIFYHOST` (which should be set to `2`, not `true`), but
I don't think this level of type safety is all that useful for the
trouble.

To bring some numbers, we currently have:

- 271 CURLOPT_* consts (CURLOPT_ - PHP Codex Search • PHP.Watch)
- 78 CURLINFO_* consts (CURLINFO_ - PHP Codex Search • PHP.Watch)
- 71 CURLE_* consts (CURLE_ - PHP Codex Search • PHP.Watch) + I sent
a PR to add 41 missing consts
(ext/curl: Add all missing CURLE constants up to Curl 8.6 by Ayesh · Pull Request #13340 · php/php-src · GitHub)
- 150+ constants for options such as protocols, HTTP versions, URL
follow rules, etc.

I think a more light-weight approach would be to:

- Move all of them to the `\Curl` namespace.
- Rename Curl options to `\Curl\Option` namespace, and rename them,
so that `CURLOPT_SSL_VERIFYHOST` becomes `Curl\Option\SSL_VERIFYHOST`
- Rename Curl error codes to similar `\Curl\Error` constants.
- Have the `CurlHandle` object accept options, e.g.
`$ch->setOption(Curl\Option\SSL_VERIFYHOST, 2)`. libcurl Easy handlers
do not have a way to retrieve the option once it's set, so there will
be no `getOption` either.
- Make Curl throw exceptions, and never `false` on `\Curl\execute()`,
with the Exception's error code and message mapped to the Curl error
code and message. We will not need to bring over `curl_error` or
`curl_errno` functions.

Realistically, I don't think we can deprecate and remove the `\curl_*`
functions any time soon, so this will actually add more maintenance
work for php-src at the end too.

Thank you.
Ayesh.

Le sam. 28 juin 2025 à 00:01, Ayesh Karunaratne ayesh@php.watch a écrit :

Hello Internals,

I’d like to formally propose a restart of the original object-oriented
curl API RFC (https://wiki.php.net/rfc/curl-oop):

https://wiki.php.net/rfc/curl_oop_v2

The prior RFC seemed to get positive feedback, with a small consensus
around wanting enum(s) for curl options. I’ve taken that feedback and
incorporated it into a new RFC, as I am not the original author, but I
am interested in making another potential improvement to the curl
extension.

In a nutshell, this version of the RFC:

  • uses enumerations for curl options and other curl constants
  • introduces a new Curl namespace

I have not yet created an implementation PR. I realize that is
somewhat discouraged, but I believe that this should be relatively
straightforward to implement (there’s also the previous RFC’s PR to
build on top of). The implementation of this RFC as it is now will
likely be tedious, however, so I’d like to get feedback on the
enumeration idea before committing to the work.

I’ve outlined one open question in the RFC, which relates to the above:

  • Should we organize the curl option enumerations by value type? Or
    have a single enumeration for all curl_setopt options and another for
    all curl_multi_setopt options?

If others (including the original RFC author) are interested in
working with me on this, I’m more than open to that, so please let me
know.

Thanks,
Eric

Thank you for taking the initiative to work on this. It’s long
overdue, and the pattern we have now — that a CurlHandle object is
passed to functions — is not as intuitive as calling a method on that
object.

However, I softly oppose this RFC in its current state and the way it
seems to be going.

I have pushed Curl and libcurl to some uncommon cases such as HTTP/3,
DoH, the new debug callback (which I authored the PR for), IMAP, and a
few other obscure tweaks. For all of them, the current curl_setopt
worked, and the more I used it, the more I understood that having just
a few Curl functions and a sea of Curl options is the least
“presumptive way”, regardless of the protocol and the options Curl
provides.

The extension is named Curl, because it’s supposed to provide Curl
functionality into PHP. It provides low-level functionality, but we
should leave it to the PHP users to build the libraries that have
fluent APIs.

This way, we can have excellent purpose-built HTTP clients, IMAP
clients, Tor proxies, etc, all built using the low-level functionality
the extension offers.The HTTP client authors know the Curl options
really well to build a secure and fast HTTP client, and someone else
building an IMAP client can provide an intuitive API but still use the
same underlying Curl functionality. I understand that your RFC does
not propose a high-level API, and I agree with you that a high-level
API will need a lot of discussion.

There’s a ton of Curl constants, and you are right that it could
really use some organizing. The upstream Curl project has not done it,
most likely because the options behave differently depending on the
protocol, and simply because it’s a lot of work and BC trouble to do
so. I don’t think we can realistically have a meaningful discourse on
how to semantically group the constants into meaningful Enums. I’d
also argue that if libcurl is OK with having a sea of Curl options, we
should not be the one to group them.

Grouping them by the expected value type is one way to do it, but as
mentioned elsewhere in replies, now, the consumers of the API will
have to figure out whether that one Curl option they always used
belongs to IntOpt or a BoolOpt. It will help with
CURLOPT_SSL_VERIFYHOST (which should be set to 2, not true), but
I don’t think this level of type safety is all that useful for the
trouble.

To bring some numbers, we currently have:

I think a more light-weight approach would be to:

  • Move all of them to the \Curl namespace.
  • Rename Curl options to \Curl\Option namespace, and rename them,
    so that CURLOPT_SSL_VERIFYHOST becomes Curl\Option\SSL_VERIFYHOST
  • Rename Curl error codes to similar \Curl\Error constants.
  • Have the CurlHandle object accept options, e.g.
    $ch->setOption(Curl\Option\SSL_VERIFYHOST, 2). libcurl Easy handlers
    do not have a way to retrieve the option once it’s set, so there will
    be no getOption either.
  • Make Curl throw exceptions, and never false on \Curl\execute(),
    with the Exception’s error code and message mapped to the Curl error
    code and message. We will not need to bring over curl_error or
    curl_errno functions.

Realistically, I don’t think we can deprecate and remove the \curl_*
functions any time soon, so this will actually add more maintenance
work for php-src at the end too.

Thank you.
Ayesh.

I’m not even sure it’s a good idea to add those namespaced options: using CURLOPT_SSL_VERIFYHOST is perfect to find the corresponding curl documentation with your favorite search engine. php’s doc is awesome, but it cannot compete with the details provided by curl’s doc on the topic.

Nicolas

I'm not even sure it's a good idea to add those namespaced options: using CURLOPT_SSL_VERIFYHOST is perfect to find the corresponding curl documentation with your favorite search engine. php's doc is awesome, but it cannot compete with the details provided by curl's doc on the topic.

Nicolas

Hi Nicolas,
You are right, if we were to rename those constants, we would lose the
"grep-ability" in both libcurl source/docs and many years of existing
open source projects and discussions.

So while keeping the existing option consts, info consts, we do not
have a lot of room to improve it. Perhaps the `CurlHandle` objects can
accept options similar to how we have `date_*` functions alongside
`DateTime` methods. I personally continue to use the functions, but I
won't mind having an OOP API. We can leave it to the library authors
(such as you, Nicolas :heart_decoration:, Symfony HTTP client is just awesome) to
design more intuitive APIs.

Ayesh.