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;

This site is a digital habitat of Sameer Borate, a freelance web developer working in PHP, MySQL and WordPress. I also provide web scraping services, website design and development and integration of various Open Source API's. Contact me at metapix[at]gmail.com for any new project requirements and price quotes.

4 Responses

1

Sameer Borate’s Blog: Creating custom stream filters | Webs Developer

June 17th, 2009 at 6:02 am

[...] a new post to his blog Sameer takes a look at streams and filters in PHP applications, specifically how to set up a custom [...]

2

Tommy

June 19th, 2009 at 9:52 am

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?

sameer

June 19th, 2009 at 9:33 pm

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.

4

Creating custom stream filters : CodeDiesel

June 22nd, 2009 at 3:03 pm

[...] is the original post: Creating custom stream filters : CodeDiesel Share and [...]

Your thoughts