In the PHP world, HTTP is very important: not only for receiving requests, which is the job of PHP application, but also for performing them. ROT web services are probably the most popular way to interface with external systems nowadays.
We’ll make no assumptions on the payload of HTTP requests (which may be binary, text, JSON, XML), so you’ll have to deal with that yourself. In some cases, you may choose a more specific solution such as the SoapClient class in the SOAP extension, or the XML-RPC extension.
There are several primitive for performing HTTP requests from PHP code:
- the cURL extension relies on the most famous library and CLI tool for performing application-level requests like HTTP and FTP ones.
- streams (stream_socket_client() or stream_context_create() as the main primitives) are an abstraction over several application protocols, like curl.
- fsockopen() and other file-based functons like fwrite() work directly at the TCP level.
- socket_*() primitives work with TCP too, and they are essentially a different API but do the same job as fsockopen().
- The HTTP PECL extension is even object-oriented, but it is less commonly available than the cURL one.
The main difference between the various solutions is their availability. Streams have been part of PHP for 10 years, while cURL is an extension that might not be present on your server. cURL is actually pretty common and even included by default in Windows versions of PHP, but the point stands with regard to the other extension.
In short, it is possible you are not in the condition to rely on cURL because you are writing a portable library, or you do not have control on the server configuration.
However, it’s probably not worth the hassle to use fsockopen() if you’re talking over HTTP, as it would be handy for lower-level textual or binary protocols. So as an alternative to cURL, many libraries (and people) stick to streams.
The problem with PHP libraries present on PEAR, SourceForge or GitHub is that there are too many of them (a picture worse than for PHP frameworks). If you choose the wrong one, you may be stuck with an old, not supported library: that’s vendor lock-in, a form of technical debt.
Look for activity in the library you choose: the commit log, number of watch and forks on Github, or tweets about the library.
These are the two libraries I’ve seen cited the most:
- Buzz by Kris Wallsmith of Symfony fame.
- Guzzle is a bit heavy in lines of code but features much documentation.
The second one contains some external dependency (Symfony components), but in any case both are managed with Composer (so you’ll need that in addition.)
Every PHP library will use one of the primitive methods we listed above, so check what backends are available before picking one.
As a good news, consider that a standard interface for HTTP clients has been proposed; this interface would make conforming libraries less riskier to use as you would always been able to switch to another implementing one in case your current dependency becomes abandoned or obsolete.
A simple way to avoid picking up new technical debt is to leverage some debt you already have. So if you’re using a framework, look at what it offers you.
For example, my last projects have involved Zend Framework as a dependency I found already inside the user space: lots of Zend_* classes at a require_once() distance for me. So I could just rely on these classes:
- Zend Framework 1: Zend_Http_Client, with back ends targeting curl and streams. So you will be portable at a price of configuring the adapter.
- Zend Framework 2: Zend\Http\Client, essentially the port of the same component with a new unified interface on top.
HTTP clients are a commodity: they have no killer feature, and they should perform their jobs at the least cost to us. So we should choose them according to non-functional metrics: project activity, API ease of use, and occasionally performance.
The testability aspect is taken care by creating a level of abstraction over these libraries, at least until a unified interface that allows for mocking of requests will be available. Note that in case of primitive functions like curl() you already have to build a little object-oriented layer of abstraction.
But you do not always have to use a library: curl() and stream_context_create() are very easy to wrap and if you only have to make a couple of types of HTTP requests, just stick to them. I do this myself in PHPUnit_Selenium, for calling the Selenium Server RC and WebDriver APIs: two Driver classes wrap the curl_*() primitive functions, which are hidden inside them and never referred upon in the rest of the codebase.
If instead the HTTP client role of your project results in dozens of different requests made by different components, check out a framework’s or a library solution to prevent the introduction of duplication derived from multiple wrapping. In this case, the only thing preventing you from including a library is your project’s policy (for example PHPUnit_Selenium ships as a portable package and as such cannot depend on non-PEAR projects without including their sources directly.)