Skip to Content
Course content

116: Working with Stream Filters

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're installing a water filtration system in your house. You don't take a giant bucket, fill it from the tap, pour it through a filter, and then move it to a different bucket. That would be exhausting and a waste of space. Instead, you install the filter inline. As the water flows from the city main to your faucet, it passes through the filter. The water is cleaned while it's moving.

PHP stream filters work exactly the same way. Instead of reading a 2GB log file into a variable, modifying the string, and saving it back—which would likely crash your server due to memory limits—you attach a filter to the stream. As you read the file chunk by chunk, PHP passes each piece through the filter before it even hits your variable. You're transforming the data in transit.

Plugging in Pre-made Filters

PHP comes with several built-in filters. Some are simple, like `string.toupper` (which makes everything uppercase) or `string.rot13`. You attach these using stream_filter_append().

// Let's say we have a text file and we want to read it as uppercase
$handle = fopen('report.txt', 'r');

// Attach the filter to the handle
stream_filter_append($handle, 'string.toupper');

while (!feof($handle)) {
    echo fread($handle, 1024); // The data is already uppercase by the time it reaches here
}
fclose($handle);

I've found that this is incredibly useful when dealing with CSVs or large exports where you need to normalize data (like forcing case sensitivity) without loading the whole dataset into an array. It's efficient, lean, and keeps your memory footprint flat.

Building Your Own Data Transformer

The real power kicks in when you create your own filter. To do this, you need to create a class that extends php_user_filter. You'll primarily be overriding the filter method, which handles the "buckets" of data as they flow through.

Let's build a "Redaction Filter." Suppose you're streaming a log file to a browser, but you need to hide any mention of the word "SECRET" for security reasons.

class RedactionFilter extends php_user_filter {
    public function filter($in, $out, &$consumed, $closing): bool {
        while ($bucket = stream_bucket_make_writeable($in)) {
            // Replace "SECRET" with "[REDACTED]" in the current chunk of data
            $bucket->data = str_replace('SECRET', '[REDACTED]', $bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return true;
    }
}

// You have to register the filter class with a name before you can use it
stream_filter_register('redact_secrets', 'RedactionFilter');

$handle = fopen('system.log', 'r');
stream_filter_append($handle, 'redact_secrets');

while ($line = fgets($handle)) {
    echo $line;
}
fclose($handle);

One thing to be careful about: since streams process data in chunks (buckets), there's a tiny chance your target word (like "SECRET") could be split right down the middle between two buckets. For simple replacements, this usually isn't an issue, but if you're building a high-precision parser, you'll need to handle those "boundary" cases by keeping a small buffer of the previous chunk's end. I'll leave that for you to ponder as you experiment.

Stacking Multiple Filters

The beauty of this system is that it's a pipeline. You can call stream_filter_append multiple times. The data will flow through the first filter, then the result will pass into the second, and so on. You could, for instance, redact secrets and then compress the output using zlib.deflate in one seamless motion.




📋 Practical Task

The Log File PII Masker

You have been tasked with creating a security tool that scrubs Personally Identifiable Information (PII) from server logs before they are sent to a third-party analysis tool. Emails should not be visible in these logs.

Your Goal: Create a custom stream filter that identifies email addresses using a regular expression and replaces them with the string <email_masked>.

Requirements:

  • Define a class that extends php_user_filter.
  • Implement the filter method using preg_replace to mask emails.
  • Register your filter with the name mask_emails.
  • Open a dummy text file containing several email addresses, apply the filter, and print the output to the screen.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.