Streams is a feature of PHP that was introduced in PHP 4.3 to unify the methods for working on files, sockets, and other similar resources. PHP 4.3 came out a long time ago, but many PHP developers never learned how to properly use streams in PHP, much to my dismay. Many use cURL for accessing HTTP resources, but I’m not a huge fan of cURL, because it has an awful interface in PHP and it presents yet another dependency. While the HTTP stream handler in PHP isn’t perfect, it works very well for most situations.
To begin, let’s consider what examples of streams could be:
- A file on a hard drive
- A HTTP connection to a website
- A UDP connection to a server
- A ZIP file
- A SSH tunnel
- A Vorbis/OGG file
What are some common operations on all of those? Primarily, they share the ability to be read from and written to. The power of PHP’s streams is that you can access all of them using the same set of functions, and if there’s something you wish to “stream-ify,” you can write your own “stream wrapper.” In addition to reading and writing, the streams framework in PHP also allows for other operations, such as renaming and deleting.
To use streams, you will have to call a PHP function that utilizes them. Stream-supporting functions that you are already familiar with include fopen(), file_get_contents(), and file(). In fact, you have already been using file streams all this time, completely transparently.
However, if you want to work with a different type of stream, you must specify it by prepending your URI with
wrapper:// to indicate the stream wrapper/protocol that you wish to use. For example, you may also be familiar with this, which access a HTTP stream (a webpage):
After wrapper:// is something that identifies what you want to open in particular. In this case, because http:// refers to websites, you enter the rest of the URL. However, for a ZIP file, it might be
zip:///path/to/file.zip. It could be something really exotic such as
unknownwrapper://access=folder4;name=Frank. Here are some more examples:
Before we continue, glance through the list of supported protocols and wrappers. Be aware that support for some wrappers depends on your configuration.
There comes a time when you need to specify a few more options than you can fit into the URL syntax. In our HTTP example above, we could provide the URL, but there was no mechanism to provide a list of headers to send. What about POST and POST variables? Stream contexts solve that problem by allowing additional options to be specified. With many of the stream-capable functions, there is a parameter to pass in a context. Let’s take a look at file_get_contents:
string file_get_contents ( string
$filename [, int
$flags = 0 [, resource
$context [, int
$offset = -1 [, int
$maxlen = -1 ]]]] )
$context parameter that you may not have noticed previously.
Contexts are created with stream_context_create, which takes in an array and returns a context resource.
$opts = array( 'http' => array( 'method' => "GET", 'header' => "Accept-language: en\r\n" . "Cookie: foo=bar\r\n" ) ); $context = stream_context_create($opts);
Using that with file_get_contents…
echo file_get_contents("http://www.example.com/", 0, $context);
To find out what the supported options are, check out the list of supported context options and parameters.
Now that we were able to pass in extra data, how about getting extra data out? If you tried the original piece of example code, it should have printed out the contents of example.com, but it did not show you the headers received. Those headers are considered a part of the metadata, and the streams framework has a function for getting that information. Before we get too ahead of ourselves, recall that file_get_contents() returns a string containing the response. We can’t “work on” a string, so we will have to be ditching our beloved friend, file_get_contents().
And say helllllo to fopen(). That returns a file pointer, and in the case of streams, a streams resource. Let’s convert that last example to use fopen():
$opts = array( 'http' => array( 'method' => "GET", 'header' => "Accept-language: en\r\n" . "Cookie: foo=bar\r\n" ) ); $context = stream_context_create($opts); $fp = fopen("http://www.example.com/", "rb", false, $context); echo stream_get_contents($fp);
Note the introduction of stream_get_contents(). We can’t directly print
$fp, since it’s a streams resource. stream_get_contents() gets the data.
To get the metadata of that request, we use stream_get_meta_data():
[wrapper_data] => Array
 => HTTP/1.1 200 OK
 => Server: Apache/2.2.3 (CentOS)
 => Last-Modified: Tue, 15 Nov 2005 13:24:10 GMT
 => ETag: “24ec5-1b6-4059a80bfd280”
 => Accept-Ranges: bytes
 => Content-Type: text/html; charset=UTF-8
 => Connection: close
 => Date: Sun, 04 Apr 2010 02:39:50 GMT
 => Age: 1774
 => Content-Length: 438
[wrapper_type] => http
[stream_type] => tcp_socket
[mode] => rb
[unread_bytes] => 0
[uri] => http://www.example.com/
[blocked] => 1
[eof] => 1
For a description of the fields, check out the documentation for stream_get_meta_data().
Filters allow you to transform the data while reading or writing it. The stream_filter_append() and stream_filter_prepend() functions attach a filter to a stream (the order that filters are applied in matters). Let’s look at an example of its use to ROT13 data that passes through a stream:
stream_filter_append($fp, "string.rot13", STREAM_FILTER_READ);
The last parameter specifies whether the filter should act on data that is being written, being read, or both.
Here’s an example (run it for fun results!):
$opts = array( 'http' => array( 'method' => "GET", 'header' => "Accept-language: en\r\n" . "Cookie: foo=bar\r\n" ) ); $context = stream_context_create($opts); $fp = fopen("http://www.example.com/", "rb", false, $context); stream_filter_append($fp, "string.rot13", STREAM_FILTER_READ); echo stream_get_contents($fp);
There are built-in filters to do simple transformations, encoding and decoding, compressing, and even encryption. See the manual for a list of available filters.
There is a special class of streams that are socket transports, that differ a little bit from the other “regular” stream wrappers. Examples include:
- Unix domain sockets
They all deal with networking, you have to use stream-based socket functions such as fsockopen() or stream_get_transports() to use those transports (file() and file_get_contents() won’t do). You should be able to figure out how to use these socket transports by reading the manual, although you naturally need to be familiar with Berkeley sockets (which are not a streams concept).
There is a list of supported stream transports.
It’s possible to create your own wrappers. I won’t go into detail about it, but the gist is that you create a class with a certain set of methods. See the manual for the methods that you need to implement.
Custom filters can also be created. The manual is very detailed on this, so check it out.
POST With Streams
Here’s an example of a POST request using streams:
$postdata = array( 'var1' => 'value1', 'var2' => 'value2', ); $opts = array('http' => array( 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query($postdata, '', '&'), 'timeout' => 5, ) ); $context = stream_context_create($opts); echo file_get_contents("http://example.com", 0, $context);