If you talk to me regularly then you already know how much I love stream wrappers in PHP. The other day I was positively giddy with how easy it was to solve a particular problem by using stream wrappers.
What are stream wrappers?
PHP 4.3.0 introduced the idea of streams. The PHP manual has a perfect explanation:
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.
Streams are identified by a particular scheme in the URI. For example, with http://someurl the scheme is 'http' and when no scheme is provided like in /file.php, the scheme is defaulted to 'file' and is expected to be a file in the filesystem (thus file:///file.php would mean the same thing).
Stream wrappers are bits of code that tell PHP how to handle certain schemes. The interesting thing is that you can create your own custom stream wrappers written in PHP with little effort.
For example, you may have done file_get_contents('file.txt'); before. But say you wanted to have the same ease of use but with data from a different source. You could create a custom stream wrapper to get data from wherever you want and use all of the normal PHP functions as usual: file_get_contents('mystream://whatever');.
Almost any function that works on files will work with custom stream wrappers. That includes fopen, fwrite, fseek, is_readable, is_writable, stat, unlink and more. In essence you can create a virtual filesystem in your applications.
Why create custom stream wrappers?
So why in the world would you want to create custom stream wrappers? Basically it comes down to decoupling filesystem-coupled interfaces from the filesystem (or any stream, for that matter). That is, make software think it's talking to files when it's talking to your custom PHP class. It's all about abstraction!
You could also use stream wrappers to simplify an interface, though this is less common. If you are using stream wrappers to simplify an interface, you can just as easily create a normal facade to simplify the interface and leave streams out of it.
For example
I wanted to use a newish PHP templating engine called Dwoo for an upcoming project. The problem was it expects files everywhere. Template files were read from the filesystem and compiled to the filesystem, and caches were written to the filesystem. There was no easy way to change this.
For this particular project I need to read templates from a string (loaded from an arbitrary source decided at runtime), and I can't use the filesystem for caches, I need to use memcached. The reason being that the application will be run in a cluster. Using shared disks is not practical with this many machines, and disk IO would eventually become a bottleneck. True, we could use RAM disks, but it's a lot easier and portable to manage the data layer from our application instead of pushing it to the OS.
Using custom stream wrappers I created two new schemes for 'tpl://' (for reading) and 'tplc://' (for writing compiled templates). I was able to fill the requirements for the project while still using the awesome templating engine with little modification. The library still thinks it's reading and writing to files but behind the scenes it's reading from a cache of template sources and writing to memcached. Perfect!
Creating custom stream wrappers
To create your own custom stream wrappers you need to write a class that implements a set of methods. After you write the class, you can register it with stream_wrapper_register. Refer to that manual page for details on the methods you need.
The only problem I encountered was how to get my custom stream working with is_readable() and is_writable(). The trick is to implement the 'url_stat' method and set the 'mode' correctly. The mode is a number that represents what type of file the file is (file or directory etc) and the file permissions -- the usual chmod values. Here's a code snippet that accurately defines how the number should add up:
-
$dir = 040000;
-
$file = 0100000;
-
-
// Now you can add any permissions you want using the usual chmod octal notation:
-
$file += 0777; // A file that is readable
-
$dir += 0222; // A directory that is writable
Leave a Reply