I would really love to implement a php_user_filter::filter(). But therefore I have to know what a bucket brigade is. This seems to be a resource which I can operate with the stream_bucket_* functions. But the documentation is not really helpful. The best I could find are those examples in stream_filter_register().
I'm especially curios what these stream_bucket_new() and stream_bucket_make_writeable() can do.
Update: It seems that PHP is exposing an internal data structure of Apache.
Ah, welcome to the least documented parts of the PHP manual! [I opened a bug report about it; maybe this answer will be helpful for documenting it: https://bugs.php.net/bug.php?id=69966]
The bucket brigade
To start with your initial question, the bucket brigade is just a name to the resource named
userfilter.bucket brigade.You are passed two different brigades in as first and second parameters to
php_user_filter::filter(). The first brigade is the input buckets you read from, the second brigade is initially empty; you write to it.Regarding your update about the data structure… It's really just a doubly linked list with strings basically. But it may well be that the name was stolen from there ;-)
stream_bucket_prepend()/stream_bucket_append()The expected
$brigadeis the output brigade aka the second parameter onphp_user_filter::filter().The
$bucketis astdClassobject like it is returned bystream_bucket_make_writable()orstream_bucket_new().These two functions just prepend or append the passed bucket to the brigade.
stream_bucket_new()To demystify this function, analyze first what it's function signature is:
First argument is the
$streamyou're writing this bucket to. Second is the$bufferthis new bucket will contain.[I'd like to note here that the
$streamparameter actually is not very significant; it's just used to check whether we need to allocate memory persistently so that it survives through requests. I just suppose that you can make PHP nicely segfault by passing a persistent stream in here, when operating on a non-persistent filter...]There is now an
userfilter.bucketresource created which is assigned to a property of a (stdClass) object namedbucket. That object has also two other properties:dataanddatalen, which contain the buffer and the buffer size of this bucket.It will return you a
stdClasswhich you can pass in tostream_bucket_prepend()andstream_bucket_append().stream_bucket_make_writable()It shifts the first bucket from the
$brigadeand returns it. If the$brigadewas emptied, it returnsnull.Further notes
When
php_user_filter::filter()is called, the$streamproperty on the objectfilter()is called on will be set to the stream we're currently working on. That's also the stream you need to pass tostream_bucket_new()when calling it. (The$streamproperty will be unset again after the call. You can't reuse it in e.g.php_user_filter::onClose()).Also note that even when you're returned a
$datalenproperty, you do not need to set that property in case you change$dataproperty before passing it tostream_bucket_prepend()orstream_bucket_append().The implementation requires you (well, it expects that or will throw a warning) that you read all the data from the
$inbucket before returning.There is another case of the documentation lying to us: in
php_user_filter::onCreate(), the$streamproperty is not set. It will only be set duringfilter()method call.Generally, don't use filters with non-blocking streams. I tried that once and it went horribly wrong … And it's not likely that's ever going to be fixed...
Sum up (examples)
Let's start with the simplest case: writing back what we got in.
All what happens here is getting buckets from
$inbucket brigade and putting it back into$outbucket brigade.Okay, now try to manipulate our input.
Now we registered the
reverse://protocol, which reverses your string (each write is being reversed on it's own here; write order is still preserved). So, we obviously now need to manipulate the bucket data and prepend it here.Now, what's the use case for
stream_bucket_new()? Usually you can just append to$bucket->data; yes, you even can concatenate all the data into the first bucket, but whenflush()'ing it might be possible that nothing is in bucket brigade and you want to send a last bucket, then you need it.With that (and the existing documentation about
php_user_filterclass), one should be able to do all sorts of magic userland stream filtering by combining all these powerful possibilities into even stronger code.