Understanding Streams in PHP
Streams are resources provided by PHP that we often use transparently, but which can also be very powerful tools. By learning how to harness their power, we can take our applications to a higher level.
The PHP manual has a great description of streams:
Streams were introduced with PHP 4.3.0 as a way of generalizing file, network, data compression, and other operations which share a common set of functions and uses. In its simplest definition, a stream is a resource object which exhibits streamable behavior. That is, it can be read from or written to in a linear fashion, and may be able to
fseek()to an arbitrary locations within the stream.
Every stream has a implementation wrapper which has the additional code necessary to handle the specific protocol or encoding. PHP provides some built-in wrappers and we can easily create and register custom ones. We can even modify or enhance the behavior of wrappers using contexts and filters.
A stream is referenced as
<scheme> is the name of the wrapper, and
<target> will vary depending on the wrapper’s syntax.
The default wrapper is
file:// which means we use a stream every time we access the filesystem. We can either write
readfile('/path/to/somefile.txt') for example or
readfile('file:///path/to/somefile.txt') and obtain the same result. If we instead use
readfile('http://google.com/') then we’re telling PHP to use the HTTP stream wrapper.
As I said before, PHP provides some built-in wrappers, protocols, and filters. To know which wrappers are installed on our machine we can use:
<?php print_r(stream_get_transports()); print_r(stream_get_wrappers()); print_r(stream_get_filters());
My installation outputs the following:
Array (  => tcp  => udp  => unix  => udg  => ssl  => sslv3  => sslv2  => tls ) Array (  => https  => ftps  => compress.zlib  => compress.bzip2  => php  => file  => glob  => data  => http  => ftp  => zip  => phar ) Array (  => zlib.*  => bzip2.*  => convert.iconv.*  => string.rot13  => string.toupper  => string.tolower  => string.strip_tags  => convert.*  => consumed  => dechunk  => mcrypt.*  => mdecrypt.* )
A nice set, don’t you think?
The php:// Wrapper
PHP has its own wrapper to access the language’s I/O streams. There are the basic
php://stderr wrappers that map the default I/O resources, and we have
php://input that is a read-only stream with the raw body of a POST request. This is handy when we’re dealing with remote services that put data payloads inside the body of a POST request.
Let’s do a quick test using cURL:
curl -d "Hello World" -d "foo=bar&name=John" http://localhost/dev/streams/php_input.php
The result of a
print_r($_POST) in the responding PHP script would be:
Array ( [foo] => bar [name] => John )
Notice that the first data pack isn’t accessible from the
$_POST array. But if we use
readfile('php://input') instead we get:
PHP 5.1 introduced the
php://temp stream wrappers which are used to read and write temporary data. As the names imply, the data is stored respectively in memory or in a temporary file managed by the underlying system.
php://filter, a meta-wrapper designed to apply filters when opening a stream with function like
<?php // Write encoded data file_put_contents("php://filter/write=string.rot13/resource=file:///path/to/somefile.txt","Hello World"); // Read data and encode/decode readfile("php://filter/read=string.toupper|string.rot13/resource=http://www.google.com");
The first example uses a filter to encode data written to disk while the second applies two cascading filters reading from a remote URL. The outcome can be from very basic to very powerful in our applications.
A context is a stream-specific set of parameters or options which can modify and enhance the behavior of our wrappers. A common use context is modifying the HTTP wrapper. This lets us avoid the use of cURL for simple network operations.
<?php $opts = array( 'http'=>array( 'method'=>"POST", 'header'=> "Auth: SecretAuthTokenrn" . "Content-type: application/x-www-form-urlencodedrn" . "Content-length: " . strlen("Hello World"), 'content' => 'Hello World' ) ); $default = stream_context_get_default($opts); readfile('http://localhost/dev/streams/php_input.php');
First we define our options array, an array of arrays with the format
$array['wrapper']['option_name'] (the available context options vary depending on the specific wrapper). Then we call
stream_context_get_default() which returns the default context and accepts an optional array of options to apply. The
readfile() statement uses these settings to fetch the content.
In the example, the content is sent inside the body of the request so the remote script will use
php://input to read it. We can access the headers using
apache_request_headers() and obtain:
Array ( [Host] => localhost [Auth] => SecretAuthToken [Content-type] => application/x-www-form-urlencoded [Content-length] => 11 )
We’ve modified the default context options, but we can create alternative contexts to be used separately as well.
<?php $alternative = stream_context_create($other_opts); readfile('http://localhost/dev/streams/php_input.php', false, $alternative);
How can we harness the power of streams in the real world? And where can we go from here? As we’ve seen, streams share some or all of the filesystem related functions, so the first use that comes to my mind is a series of virtual filesystem wrappers to use with PaaS providers like Heroku or AppFog that don’t have a real filesystem. With little or no effort we can port our apps from standard hosting services to these cloud services and enjoy the benefits. Also – and I’ll show in a follow-up article – we can build custom wrappers and filters for our applications that implementing custom file formats and encoding.