8.4 Creating Wrappers

You can write custom wrappers in PHP. This lets you extend PHP to "speak" any protocol you want while still letting you use the standard stream functions, such as file_get_contents( ).

Stream wrappers are implemented as a class with specific methods. These methods are called by the stream-handling code when a stream using your wrapper is opened, read from, written to, and so forth.

In all, there are 17 potential methods that make up a complete wrapper, but it's rare to actually code all of these. Table 8-2 contains a list of wrapper methods and when they're evoked.

Table 8-2. Wrapper methods



PHP 5 only?

boolean stream_open(string path, string mode, int options, string opened_path)

fopen( )


void stream_close(void)

fclose( )


string stream_read(int count)

fread( ), fgets( )


int stream_write(string data)

fwrite( ), fputs( )


boolean stream_eof(void)

feof( )


int stream_tell(void)

ftell( )


boolean stream_seek(int offset, int whence)

fseek( )


boolean stream_flush(void)

fflush( )


array stream_stat(void)

fstat( )


boolean unlink(string path)

unlink( )


boolean mkdir(string path, int mode, int options)

mkdir( )


boolean rmdir(string path, int options)

rmdir( )


boolean dir_opendir(string path, int options)

opendir( )


string dir_readdir(void)

readdir( )


boolean dir_rewinddir(void)

rewinddir( )


boolean dir_closedir(void)

closedir( )


array url_stat(string path, int flags)

stat( )


In many cases, a method will have no meaningful concept for a wrapper, or will be impossible to implement due to technical limitations. For example, you cannot rename a file using HTTP.

Also, some higher-level functions, such as file_get_contents( ) and file_put_contents( ), call multiple lower-level streams functions. For instance, you can't call file_get_contents( ) if you haven't implemented stream_open( ), stream_read( ), and stream_close( ).

If you try to call a method that's not defined by your wrapper, PHP issues a warning. Here's an example using a contrived fake wrapper that has no methods:


PHP Warning:  file_get_contents(fake://): failed to open stream: 

  "fake::stream_open" call failed

The wrapper method specifications are like an informal interface. The big difference is that you're not required to implement all the methods.

This section shows how to implement a wrapper for PHP's shared memory functions. Shared memory is a way to store data in your server's RAM. When it's stored in RAM, you can access the information very quickly because you don't need to read from your disk drive.

Additionally, as the name suggests, this data is easily shared among different programs. This can be multiple copies of PHP running on the same machine, such as in a typical web server setup. Or, it can be other programs written in a different language.

The downside to using shared memory is that it takes up valuable RAM space, so you don't want to store very large files in shared memory. Also, the data is lost completely when the machine is rebooted. Finally, to use shared memory with Windows, you must run Windows 2000 or greater and run PHP as module. It won't work with the CGI or CLI modules.

PHP has a couple of different ways to interact with shared memory. This wrapper uses the shmop extension because it's most similar to the standard file interface and thus requires the least work to fit it to the stream's interface.

The shmop extension is included in PHP, but it's not enabled by default. To turn it on, add the --enable-shmop flag when compiling PHP.

8.4.1 Opening and Closing

A wrapper is a class with particular methods. Register a class as a wrapper using stream_wrapper_register( ). This call registers the shmop protocol with the ShmopWrapper class:

stream_wrapper_register('shmop', 'ShmopWrapper');

The first argument is the name of the protocol and the second is the class name. If stream_wrapper_register( ) cannot register the class, it returns false.

It's vital to implement stream_open( ) and stream_close( ) because you can't work with streams without them?even file_get_contents( ) implicitly calls stream_open( ) and stream_close( )

The stream_open( ) method tries to open the file (or, in this example, the shared memory segment). If it's successful, it returns true; it returns false if there's an error. The stream_close( ) method closes the file.

Example 8-2 shows how you implement stream_open( ) and stream_close( ) for ShmopWrapper.

Example 8-2. ShmopWrapper: stream_open( ) and stream_close( )
class ShmopWrapper {

    private $id;

    private $position;

    private $length;


    public function stream_open($path, $mode = "c", $options, &$opened_path) {

        // hardcode some values for now

        $project = 'p';   // project PHP

        $perms   = 0600;  // user read and write only

        $size    = 16384;

        list($scheme, $target) = explode('://', $path);

        // convert path to key

        if (-1 =  =  = ($key = ftok($target, $project))) {

            if ($options & STREAM_REPORT_ERRORS) {

                trigger_error("Cannot convert path to shmop key.", 



            return false;



        // mode fix ups for file_(get|put)_contents( )

        if ($mode =  = 'rb') {

            $mode = 'c';

        } elseif ($mode =  = 'wb') {

            $mode = 'w';


        // illegal mode

        if (strlen($mode) != 1 || stripos("acwn", $mode) =  =  = false) {

            if ($options & STREAM_REPORT_ERRORS) {

                trigger_error("Illegal mode. Must be one of: a, c, w, n.", 



            return false;



        if (! $this->id = shmop_open($key, $mode, $perms, $size)) {

            if ($options & STREAM_REPORT_ERRORS) {

                trigger_error("Cannot open shmop segment.", E_USER_WARNING);


            return false;



        $this->position = 0;

        $this->length = shmop_size($this->id);

        return true;


    public function stream_close( ) {




The stream_open( ) method takes four parameters: $path, $mode, $options, and &$opened_path.

The $path variable is the complete path passed to fopen( ), including the protocol?for example, shmop://www/www.example.com/index.html. The $mode specifies how the stream is to be opened. This is one of four potential values:


Access: open an existing segment for read-only access.


Create: create a new segment, or open an existing segment for read and write access.


Write: open an existing segment for read and write access, but do not create a new segment.


New: create a new segment, but fail if the segment already exists.

The third parameter is either or both of these two options:


Search the include_path when passed a relative path. If the file is found in the include_path and opened, set the fourth parameter, &$opened_path, to its full pathname.


Report errors using trigger_error( ).

The stream_open( ) function in Example 8-2 first breaks apart the $path into the $scheme and the $target. The $scheme should always be shmop, and the $target should be a full pathname to a file.

This pathname is then converted to a shmop key using ftok( ). The ftok( ) function converts a filename and a project identifier into a unique key for shmop_open( ). This key distinguishes one shared memory segment from the others. The project identifier must be a one-letter code. For now, the project identifier is always p, for PHP.

When ftok( ) fails, it returns -1. When this occurs, stream_open( ) issues an error if the STREAM_REPORT_ERRORS constant is set in $options. This condition is checked by logically ANDing the two values together. The method then returns false and exits.

The next step is checking that the mode is a single digit long and contains one of the four acceptable modes: a, c, n, or w. If this fails, a similar series of error handlers kicks in.

You're finally ready to open the shared memory segment by calling shmop_open( ). This function takes four parameters. You've already seen the $key and the $mode, but it also uses the $perms and $size arguments.

The $perms parameter is the set of file permissions to use when creating the segment, and the $size parameter is the size of the segment to create in bytes. Like the project identifier, these two values are currently fixed, but later on these will be editable using stream context options.

When everything goes okay, the shared memory identifier is stored in the id property and the method returns true. If this fails, it returns false.

Thankfully, the stream_close( ) function is less complex. It releases the connection using shmop_close( ) and doesn't return anything.

8.4.2 Reading and Writing

Now that you can open and close the stream, the next step is to enable reading and writing. Example 8-3 shows this.

Example 8-3. ShmopWrapper: stream_read( ) and stream_write( )
   public function stream_read($count) {

        // don't read beyond the end of the file

        $count = min($count, $this->length - $this->position);

        $data = shmop_read($this->id, $this->position, $count);

        $this->position += strlen($data);

        return $data;


    public function stream_write($data) {

        $bytes_written = shmop_write($this->id, $data, $this->position);

        $this->position += $bytes_written;

        return $bytes_written;


When you call fread( ) and fwrite( ), the streams-handling code invokes your wrapper's stream_read( ) and stream_write( ) methods.

The fread( ) method takes one parameter, $count. This is the number of bytes to read from the stream; however, you're responsible for checking that this number isn't greater than the remaining bytes left in the stream. When $count is too big, the call to min( ) resets it to $this->length - $this->position, which is the maximum number of remaining bytes.

You now read the data from the stream with shmop_read( ). This function takes the shared memory handle, the position in the segment to read from, and the number of bytes to return. It returns the data stored in that portion of the segment.

When you read data, you must update your position in the stream by the size of your read. This way, you can read chunks of data from the stream and eventually reach the end. If you don't adjust the position, you just read the same segment again and again.

To account for the read, increment $this->position by the string length of the data. Once that's done, return the data.

Writing to shared memory is a similar process, but instead of taking the number of bytes to read and returning data, it takes data and returns the number of bytes written. It also updates the position property.

You're almost done, but there are still three necessary methods remaining?stream_tell( ), stream_eof( ), and stream_seek( )?which are contained in Example 8-4. They help the stream know where it is, see if it's at the end of the file, and navigate through the file.

Example 8-4. ShmopWrapper: stream_tell( ), stream_eof( ), stream_seek( )
    public function stream_tell( ) {

        return $this->position;


    public function stream_eof( ) {

        return $this->position >= $this->length;


    public function stream_seek($offset, $whence) {

        switch($whence) {

        case SEEK_SET:

            if ($offset >= 0 && $offset < $this->length) {

                 $this->position = $offset;

                 return true;

            } else {

                 return false;




        case SEEK_CUR:

            if ($offset >= 0 &&

                $this->$position + $offset < $this->length) {

                 $this->position += $offset;

                 return true;

            } else {

                 return false;




        case SEEK_END:

            if ($this->length + $offset >= 0) {

                 $this->position = $this->length + $offset;

                 return true;

            } else {

                 return false;





            return false;



Because you're already tracking your position, the first method, stream_tell( ), is essentially an accessor for the $position property.

The stream_eof( ) method reports when you're at the end of the segment. This occurs when the current position is greater than or equal to the size of the shared memory segment. The position should never exceed the $length, but it's better to be safe than sorry.

The last method is the most complex because it's really handling three different jobs. While the primary goal of stream_seek( ) is moving to a new location in the stream, you can specify that position in three different ways: SEEK_SET, SEEK_CUR, and SEEK_END. Here's how they're related:


Move $offset spaces from the beginning of the stream.


Move $offset spaces from the current position of the stream.


Move $offset spaces from the end of the stream. This value should be negative, so you move back into the stream instead of past the end.

The stream_seek( ) method uses a switch statement because the three types are handled similarly. After the request passes some out-of-bounds checking, to prevent moving past the beginning or end of the segment, the position is updated and the method returns true. Bad seek requests generate a return value of false.

8.4.3 Using ShmopWrapper

Now that you've implemented the basics necessary to get the wrapper up and running, use it:

stream_register_wrapper("shmop", "ShmopStream")

    or die("Failed to register shmop wrapper");

$file = _ _FILE_ _; // pass current file to key generator

file_put_contents("shmop://$file", 'Rasmus Lerdorf');

print file_get_contents("shmop://$file");

Rasmus Lerdorf

This example saves the string Rasmus Lerdorf to a shared memory segment and then retrieves it and prints it out. It uses the special _ _FILE_ _ constant as the pathname because it ensures the path will always be valid.

Since this value (Rasmus Lerdorf) is stored in shared memory, it's available on subsequent requests:

stream_register_wrapper("shmop", "ShmopWrapper")

    or die("Failed to register shmop wrapper");

$file = _ _FILE_ _; // pass current file to key generator

print file_get_contents("shmop://$file");

Rasmus Lerdorf

This code wrote nothing to the segment: the value persisted from the previous example.

The shmop wrapper also works with the lower-level streams manipulation functions:

stream_register_wrapper("shmop", "ShmopWrapper")

    or die("Failed to register shmop wrapper");

$file = _ _FILE_ _; // pass current file to key generator

$shmop = fopen("shmop://$file", 'c') or die("Can't open segment.");

fwrite($shmop, 'Zeev Suraski');


print fread($shmop, 20);


Zeev Suraskirf

Surprise! The old value of Rasmus Lerdorf was still stored in the segment, so calling fwrite( ) with Zeev Suraski only overwrote the first 12 letters. This left the last two?rf?in the segment. That's why you got the nonsense value of Zeev Suraskirf when you printed out the first 20 characters.

8.4.4 Deleting

A new feature in PHP 5 is the ability to delete files using streams. In PHP 4, you can only read and write data, not delete it, so it was impossible to clean up existing resources.

True to PHP's Unix roots, the method to delete an item is called unlink( ), as shown in Example 8-5.

Example 8-5. ShmopWrapper: unlink( )
    public function unlink($path) {

        if ($this->stream_open($path, 'w', 0, $opened_path) && 

            shmop_delete($this->id) && 

            $this->stream_close( )) {

            return true;


        return false;


The unlink( ) method from Example 8-5 takes a path to delete, such as shmop:///www/www.example.com/index.php. However, the shmop_delete( ) function needs a shared memory resource.

To circumvent this problem, unlink( ) calls stream_open( ) to convert the path to a segment. Now the id property holds the segment resource, which is passed to shmop_delete( ). Finally, to clean up, the segment is closed.

Adding this method to your class lets you remove segments:

stream_register_wrapper("shmop", "ShmopWrapper")

    or die("Failed to register shmop wrapper");

$file = _ _FILE_ _; // pass current file to key generator


$shmop = fopen("shmop://$file", 'c') or die("Can't open segment.");

fwrite($shmop, 'Zeev Suraski');


print fread($shmop, 20);


Zeev Suraski

Everything works okay now because the segment was cleared out before you wrote Zeev Suraski.

8.4.5 Adding Context Options

There's still one problem with the wrapper. Back in stream_open( ), you hardcoded values for the project name, the segment permissions, and the segment size. There isn't a good way to specify alternative values using the regular wrapper://target syntax.

That's why streams implements stream contexts. You can use a context to modify the three fixed values. First, here's how to pass context values to the wrapper:

$options = array(

    'shmop' => array(

        'size' => 32768



$context = stream_context_create($options);

file_put_contents("shmop://$file", $data, false, $context)

    or die("Can't open segment.");

As with the http wrapper, place your options in a multidimensional array. The first array has a key of shmop; the second array can have keys of project, perms, and size. This example sets the size option to 32768 but leaves the other two options untouched.

After converting the array to a context with stream_context_create( ), pass it to file_put_contents( ) as the third parameter.

Here's a revised start of stream_open( ) from Example 8-2 that processes the context options:

public function stream_open($path, $mode = "c", $options, &$opened_path) {

        $contexts = stream_context_get_options($this->context);

        $context = $contexts['shmop'];

        $project = empty($context['project']) ? 'p'   : $context['project'];

        $perms   = empty($context['perms'])   ? 0600  : $context['perms'];

        $size    = empty($context['size'])    ? 16384 : $context['size'];

        // rest of method follows...


When a person passes a context, the wrapper object automatically stores it in the $context property. Convert this context resource back into an array using stream_context_get_options( ). The shmop options, if any, are stored in the shmop key of the array.

Once these options are stored in $context, you need to check whether each option is set. If it is, use its values; otherwise, use the default value. This is done using the ? : ternary operator.

The most important part of adding context options is documentation. There's no standard naming convention, because options vary from wrapper to wrapper. So, without documentation, nobody will know what options they can use. There's no documentation standard for wrappers, but PhpDocumentor (http://pear.php.net/package/PhpDocumentor) is a good choice.

8.4.6 Other Methods

This shmop wrapper implements eight different wrapper methods:

  • stream_open( )

  • stream_close( )

  • stream_read( )

  • stream_write( )

  • stream_tell( )

  • stream_eof( )

  • stream_seek( )

  • unlink( )

However, that's less than half of the total number of available methods. You can also implement:

  • stream_flush( )

  • stream_stat( )

  • mkdir( )

  • rmdir( )

  • dir_opendir( )

  • dir_readdir

  • dir_rewinddir( )

  • dir_closedir

  • url_stat( )

Most of these methods operate on directories, but a few, such as stream_flush( ), work on files.

For more information on the other wrapper methods, consult Table 8-2 or read the documentation for stream_wrapper_register( ) in the PHP Manual at http://www.php.net/stream-wrapper-register.