Creating custom stream filters

In this post we will see how to create a custom stream filter. Streams, first introduced in PHP 4.3, provide an abstration layer for file access. A number of different resources besides files – like network connections, compression protocols etc. can be regarded as “streams” of data which can be serially read and written to.

By default there are a number of filters registered with PHP. You can get the list of filters registered on your system by the following call:

print_r (stream_get_filters());

On my system it returns the following registered filters.

Array
(
    [0] => convert.iconv.*
    [1] => string.rot13
    [2] => string.toupper
    [3] => string.tolower
    [4] => string.strip_tags
    [5] => convert.*
    [6] => consumed
    [7] => zlib.*
    [8] => bzip2.*
)

For example to strip html tags from a input string you add the ‘string.strip_tags’ filter to the stream resource as below.

$html = 'The <b>World</b> is safe <i>again</i>.';
 
$fp = fopen("php://output", "r");
stream_filter_prepend($fp, "string.strip_tags");
fwrite($fp, $html);
fclose($fp);

Which will than output:

The World is safe again.

Creating a custom filter
In this section we will create a filter to replace urls in the input stream with a
‘[—-URL—-]’ string. Now this may not look like much of a helpful filter, but this is just for illustrative purpose. You can use any replacement string you like. The complete source for the filter class is shown below.

<?php
 
class URLFilter extends PHP_User_Filter
{
    private $_data;
 
    /* Called when the filter is initialized */
    function onCreate( )
    {
        $this->_data = '';
        return true;
    }
 
    /* This is where the actual stream data conversion takes place */
    public function filter($in, $out, &$consumed, $closing)
    {
 
        /* We read all the stream data and store it in 
           the '$_data' variable 
        */
        while($bucket = stream_bucket_make_writeable($in))
        {
            $this->_data .= $bucket->data;
            $this->bucket = $bucket;
            $consumed = 0;
        }
 
        /* Now that we have read all the data from the stream we process 
          it and save it again to the bucket.
        */
        if($closing)
        {
            $consumed += strlen($this->_data);
 
            $pattern = "#http(s)?://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?#";
            $str = preg_replace($pattern,
                                '[----URL----]',
                                $this->_data);
 
            $this->bucket->data = $str;
            $this->bucket->datalen = strlen($this->_data);
 
            if(!empty($this->bucket->data))
                stream_bucket_append($out, $this->bucket);
 
            return PSFS_PASS_ON;
        }
 
        return PSFS_FEED_ME;
    }
}
 
?>

All the action takes place in the filter() method, where we collect all the data from the buckets. The data is retrieved in the fashion of a bucket brigade. The stream_bucket_make_writable() function reads a portion of the data from the input bucket and converts it to a PHP bucket object. The data property of the bucket object is a string holding the bucket’s data, whereas datalen stores its length. We could convert the data in the filter() function itself, but rather we store all the data retrieved in the $_data variable and convert it at a single go.

Once all the data from the stream is read the $closing parameter is set to true. Now we can check for that and than process the data in any fashion we like. Once the data processing is done we add the processed data to the bucket and return a PSFS_PASS_ON to imply that we have successfully processed the stream data.

An Example
Before we use the filter we have to register the filter and append it to the stream. A example using the above class is shown below, which gets the content from Google.com and than replaces all the urls in the source with the ‘[—-URL—-]’ string.

 
stream_filter_register('myFilter', 'URLFilter');
$handle = fopen("http://www.google.com/", "r");
stream_filter_append($handle, "myFilter");
 
while (!feof($handle))
{
  $contents .= fread($handle, 8192);
}
 
fclose($handle);
echo $contents;


4 thoughts on “Creating custom stream filters

  1. This was all a bit new to me :) Could you say something about the strenghts of this approach as apposed to manipulating the data in a variable?

  2. Well it may look like manipulating data in a variable is preferable to the above. But the above is just a simple example. Once you add a filter to a stream it basically hides all the implementation details from the user. You will be unaware of the data being manipulated in a stream.

    And also the same filter can be used with any stream (files, urls, various protocols etc.) without any changes to the underlying code.

    Also multiple filters can be chained together, so that the output of one can be the input of another.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>