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

On Fri, Jun 27, 2025, at 4:58 PM, Ayesh Karunaratne wrote:

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

So I think we've identified a key disagreement about not just the goal, but the intent. To what extent should PHP core ship with a usable HTTP client?

Right now it ships with Curl, which... in its current form is not usable. It's a low-level tool with an inscrutable API we inherited from C. It's not viable as a user-facing tool. It's a tool that's only useful to people writing client libraries like Guzzle or CurlClass or Symfony HTTP or such.

On the one hand, Marco is correct that for a web-centric language to not ship with a non-sucky way to send web requests is... kinda embarrassing. Even if the use cases where you can't install a 3rd party library are few, they are non-zero. And that also doesn't help new users figure out what to use. (Eg, the person who wrote most of the code for our main application at work, learning PHP as he went, is sending lots of requests using... an ungodly mess of curl that can't even understand. Because he didn't know that things like Guzzle even existed.)

On the other hand, Ben is correct that an HTTP client is a not-small task, with a very deep rabbit hole.

So there's two closely related but distinct asks here:

1. Make working with curl suck less (giving it an OOP interface is part of that but not all)
2. Ship a useful first-party HTTP client that can handle the 80% case, even if it's not full featured.

Beefing up Curl's interface until it fulfills part 2 is one approach, but not the only.

At one extreme would be the "do nothing, status quo is fine" position. Ayesh seems to be close to that position, maybe with a little polish for funsies. The other extreme would essentially be "Guzzle in core," which I don't think anyone is advocating. Where between those extremes we should land is debatable.

Personally I'm of the mind that a simple, basic-features HTTP client in core would be a good thing; that's central enough that it should not be left to userland. It doesn't need to offer every possible feature; no need for async multiplexing, for example. But sending GET and POST requests with straightforward bodies should be table-stakes for a web language, and right now, that's a second class citizen. If it's written in such a way that it can be extended easily in user-space, so much the better.

Whether that basic-features client is Curl itself or a bundled wrapper that uses Curl, I have no strong preference. The challenge of making it separate from Curl is, shocker, that it's bikeshed bait. Does that imply using the new URL/URI classes? Does it imply we need request/response objects? The rabbit hole indeed gets deep fast.

So the first question, I think, is what is the consensus between these three coarse-grained positions:

1. Status quo is fine. PHP core not having a user-friendly way to send HTTP requests is acceptable. Maybe make Curl a little nicer, but only to make life easier for Guzzle et al.
2. We should develop the Curl API until it's usable for basic HTTP behavior, but no further.
3. We should bundle an HTTP client that wraps Curl (with or without minor improvements to Curl), exact scope TBD.

Personally, I'm open to either 2 or 3. 3 is more bikesheddable, but possibly the better end result.

Where does everyone else stand?

--Larry Garfield

On Jun 28, 2025, at 00:01, Larry Garfield <larry@garfieldtech.com> wrote:

On Fri, Jun 27, 2025, at 4:58 PM, Ayesh Karunaratne wrote:

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

So I think we've identified a key disagreement about not just the goal, but the intent. To what extent should PHP core ship with a usable HTTP client?

Right now it ships with Curl, which... in its current form is not usable. It's a low-level tool with an inscrutable API we inherited from C. It's not viable as a user-facing tool. It's a tool that's only useful to people writing client libraries like Guzzle or CurlClass or Symfony HTTP or such.

On the one hand, Marco is correct that for a web-centric language to not ship with a non-sucky way to send web requests is... kinda embarrassing. Even if the use cases where you can't install a 3rd party library are few, they are non-zero. And that also doesn't help new users figure out what to use. (Eg, the person who wrote most of the code for our main application at work, learning PHP as he went, is sending lots of requests using... an ungodly mess of curl that can't even understand. Because he didn't know that things like Guzzle even existed.)

On the other hand, Ben is correct that an HTTP client is a not-small task, with a very deep rabbit hole.

So there's two closely related but distinct asks here:

1. Make working with curl suck less (giving it an OOP interface is part of that but not all)
2. Ship a useful first-party HTTP client that can handle the 80% case, even if it's not full featured.

Beefing up Curl's interface until it fulfills part 2 is one approach, but not the only.

At one extreme would be the "do nothing, status quo is fine" position. Ayesh seems to be close to that position, maybe with a little polish for funsies. The other extreme would essentially be "Guzzle in core," which I don't think anyone is advocating. Where between those extremes we should land is debatable.

Personally I'm of the mind that a simple, basic-features HTTP client in core would be a good thing; that's central enough that it should not be left to userland. It doesn't need to offer every possible feature; no need for async multiplexing, for example. But sending GET and POST requests with straightforward bodies should be table-stakes for a web language, and right now, that's a second class citizen. If it's written in such a way that it can be extended easily in user-space, so much the better.

Whether that basic-features client is Curl itself or a bundled wrapper that uses Curl, I have no strong preference. The challenge of making it separate from Curl is, shocker, that it's bikeshed bait. Does that imply using the new URL/URI classes? Does it imply we need request/response objects? The rabbit hole indeed gets deep fast.

So the first question, I think, is what is the consensus between these three coarse-grained positions:

1. Status quo is fine. PHP core not having a user-friendly way to send HTTP requests is acceptable. Maybe make Curl a little nicer, but only to make life easier for Guzzle et al.
2. We should develop the Curl API until it's usable for basic HTTP behavior, but no further.
3. We should bundle an HTTP client that wraps Curl (with or without minor improvements to Curl), exact scope TBD.

Personally, I'm open to either 2 or 3. 3 is more bikesheddable, but possibly the better end result.

Where does everyone else stand?

--Larry Garfield

I’m also open to 2 or 3, but 3 sounds more like something that I think should live in userland, so I lean much more towards 2.

Cheers,
Ben

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

So I think we’ve identified a key disagreement about not just the goal, but the intent. To what extent should PHP core ship with a usable HTTP client?

Right now it ships with Curl, which… in its current form is not usable. It’s a low-level tool with an inscrutable API we inherited from C. It’s not viable as a user-facing tool. It’s a tool that’s only useful to people writing client libraries like Guzzle or CurlClass or Symfony HTTP or such.

On the one hand, Marco is correct that for a web-centric language to not ship with a non-sucky way to send web requests is… kinda embarrassing. Even if the use cases where you can’t install a 3rd party library are few, they are non-zero. And that also doesn’t help new users figure out what to use. (Eg, the person who wrote most of the code for our main application at work, learning PHP as he went, is sending lots of requests using… an ungodly mess of curl that can’t even understand. Because he didn’t know that things like Guzzle even existed.)

On the other hand, Ben is correct that an HTTP client is a not-small task, with a very deep rabbit hole.

So there’s two closely related but distinct asks here:

  1. Make working with curl suck less (giving it an OOP interface is part of that but not all)
  2. Ship a useful first-party HTTP client that can handle the 80% case, even if it’s not full featured.

Beefing up Curl’s interface until it fulfills part 2 is one approach, but not the only.

At one extreme would be the “do nothing, status quo is fine” position. Ayesh seems to be close to that position, maybe with a little polish for funsies. The other extreme would essentially be “Guzzle in core,” which I don’t think anyone is advocating. Where between those extremes we should land is debatable.

Personally I’m of the mind that a simple, basic-features HTTP client in core would be a good thing; that’s central enough that it should not be left to userland. It doesn’t need to offer every possible feature; no need for async multiplexing, for example. But sending GET and POST requests with straightforward bodies should be table-stakes for a web language, and right now, that’s a second class citizen. If it’s written in such a way that it can be extended easily in user-space, so much the better.

Whether that basic-features client is Curl itself or a bundled wrapper that uses Curl, I have no strong preference. The challenge of making it separate from Curl is, shocker, that it’s bikeshed bait. Does that imply using the new URL/URI classes? Does it imply we need request/response objects? The rabbit hole indeed gets deep fast.

So the first question, I think, is what is the consensus between these three coarse-grained positions:

  1. Status quo is fine. PHP core not having a user-friendly way to send HTTP requests is acceptable. Maybe make Curl a little nicer, but only to make life easier for Guzzle et al.
  2. We should develop the Curl API until it’s usable for basic HTTP behavior, but no further.
  3. We should bundle an HTTP client that wraps Curl (with or without minor improvements to Curl), exact scope TBD.

Personally, I’m open to either 2 or 3. 3 is more bikesheddable, but possibly the better end result.

Where does everyone else stand?

–Larry Garfield

I think #2 is the way to go. I personally just use Symfony’s HTTP Client 99.9% of the time - there is one exception in recent years where I had to go low-level cURL and craft an extremely specific HTTP request. Yes, it is a SOAP service for a government system, because of course it is.

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.

Valid. This cannot be avoided if any kind of RFC is to be accepted, so I think the next best solution is to put effort into documentation and ensure that things are referenced (like the new Enum options having very obvious links to the original constants and to the curl documentation), making discoverability easy.
The good thing is that OOP API is purely new, so there is not going to be tons of old content about it to make things messy.

···

Arvīds Godjuks+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius

Hi

Am 2025-06-26 18:53, schrieb Larry Garfield:

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.

More specifically, since it does introduce an entirely new namespace it should be considered a “new extension” and must therefore follow the Exception policy outlined in: policies/coding-standards-and-naming.rst at main · php/policies · GitHub

Best regards
Tim Düsterhus

More specifically, since it does introduce an entirely new namespace it
should be considered a “new extension” and must therefore follow the
Exception policy outlined in:
policies/coding-standards-and-naming.rst at main · php/policies · GitHub

Embarrassingly, I had read your RFC adding that section - it's kind of
what got me thinking about adding a curl namespace in the first place!
- but I guess I didn't put it into long-term memory. Thanks for the
catch, I'll update that when I can.

Thanks for your response Ayesh! I hope to see this RFC on php.watch :slight_smile:

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.

It would be helpful for me to understand what specifically you are
objecting to in the current discussion, putting aside the suggested
enumeration changes. Do you object to any HTTP-oriented methods at
all, like something to set the HTTP method? Would you object to
methods that apply to all, or nearly all, transport types, like a
setTimeout() method?

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.

Acknowledged, thank you. The RFC does propose what you're suggesting
with never returning false and instead throwing exceptions, but it
feels like it might still be useful to have properties on the actual
object. Do you have opinions on that?

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.

Agreed, I do not plan on suggesting this, not even under Future Scope.

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.

That's a good point, but I do feel like there is value in having these
under a namespace; a part of the reason I am interested in this
proposal is that I'd like to see more of PHP core living under
namespaces, since I think it will present a more consistent vision of
the "standard library". I wonder if there is a way we could preserve
the link, at least? In my proposal, the enum values are equivalent to
their curl constant equivalents, which I thought would help with
discoverability in the documentation.

For the record - and I'll say this elsewhere - based on the discussion
so far I am planning on withdrawing the proposal for separate enums.
I'm still considering using a single enum to group all of these
options, however, instead of constants.

Thanks for your thoughtful response, Larry! I agree with your summary.

1. Status quo is fine. PHP core not having a user-friendly way to send HTTP requests is acceptable. Maybe make Curl a little nicer, but only to make life easier for Guzzle et al.
2. We should develop the Curl API until it's usable for basic HTTP behavior, but no further.
3. We should bundle an HTTP client that wraps Curl (with or without minor improvements to Curl), exact scope TBD.

Personally, I'm open to either 2 or 3. 3 is more bikesheddable, but possibly the better end result.

Where does everyone else stand?

Based on the feedback so far (I do plan on waiting for more responses
to your email), and on my own preferences, I wonder if there is a
hybrid option I could propose. Perhaps the RFC could offer both a
\Curl\Handle (tentative name) to address position 1, and a
\Curl\BasicHttpHandle (also tentative name) that addresses position 2?
If people were amenable, I'd even make the BasicHttpHandle a separate
vote.

I agree that 3 is both more bikesheddable and also possibly ideal, but
I feel my above suggestion maybe strikes the right balance between the
"status quo is fine, I don't want to see random HTTP-related methods
on my low(est)-level curl object" and the "I'd like to do basic HTTP
stuff with curl, without a library" crowds.

Barring that, my preference would be 2, but I'd accept 1 just to have
it pass - like I mentioned elsewhere, I think there is value in
introducing namespaces and object-oriented APIs for "modernization"
and language consistency reasons.