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.
Definition of bucket brigade : a chain of persons acting to put out a fire by passing buckets of water from hand to hand.
HOW DO THEY WORK? A Bucket Brigade is a fire-fighting technique of passing water, one stage at a time and is analogous to the function of a Bucket Brigade Device, or BBD. A Bucket Brigade Device (or BBD) is an analog circuit contained within a small chip that delays an incoming audio signal.
The name is derived from an old fire-mans technique where a line of people would pass buckets of water.
In most signal processing applications, bucket brigades have been replaced by devices that use digital signal processing, manipulating samples in digital form.
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]
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()
stream_bucket_prepend(resource $brigade, stdClass $bucket): null stream_bucket_append(resource $brigade, stdClass $bucket): null
The expected $brigade
is the output brigade aka the second parameter on php_user_filter::filter()
.
The $bucket
is a stdClass
object like it is returned by stream_bucket_make_writable()
or stream_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:
stream_bucket_new(resource $stream, string $buffer): stdClass
First argument is the $stream
you're writing this bucket to. Second is the $buffer
this new bucket will contain.
[I'd like to note here that the $stream
parameter 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.bucket
resource created which is assigned to a property of a (stdClass
) object named bucket
. That object has also two other properties: data
and datalen
, which contain the buffer and the buffer size of this bucket.
It will return you a stdClass
which you can pass in to stream_bucket_prepend()
and stream_bucket_append()
.
stream_bucket_make_writable()
stream_bucket_make_writeable(resource $brigade): stdClass|null
It shifts the first bucket from the $brigade
and returns it. If the $brigade
was emptied, it returns null
.
When php_user_filter::filter()
is called, the $stream
property on the object filter()
is called on will be set to the stream we're currently working on. That's also the stream you need to pass to stream_bucket_new()
when calling it. (The $stream
property 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 $datalen
property, you do not need to set that property in case you change $data
property before passing it to stream_bucket_prepend()
or stream_bucket_append()
.
The implementation requires you (well, it expects that or will throw a warning) that you read all the data from the $in
bucket before returning.
There is another case of the documentation lying to us: in php_user_filter::onCreate()
, the $stream
property is not set. It will only be set during filter()
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...
Let's start with the simplest case: writing back what we got in.
class simple_filter extends php_user_filter { function filter($in, $out, &$consumed, $closing) { while ($bucket = stream_bucket_make_writeable($in)) { $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); } return PSFS_PASS_ON; } } stream_filter_register("simple", "simple_filter")
All what happens here is getting buckets from $in
bucket brigade and putting it back into $out
bucket brigade.
Okay, now try to manipulate our input.
class reverse_filter extends php_user_filter { function filter($in, $out, &$consumed, $closing) { while ($bucket = stream_bucket_make_writeable($in)) { $consumed += $bucket->datalen; $bucket->data = strrev($bucket->data); stream_bucket_prepend($out, $bucket); } return PSFS_PASS_ON; } } stream_filter_register("reverse", "reverse_filter")
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 when flush()
'ing it might be possible that nothing is in bucket brigade and you want to send a last bucket, then you need it.
class append_filter extends php_user_filter { public $stream; function filter($in, $out, &$consumed, $closing) { while ($bucket = stream_bucket_make_writeable($in)) { $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); } // always append a terminating \n if ($closing) { $bucket = stream_bucket_new($this->stream, "\n"); stream_bucket_append($out, $bucket); } return PSFS_PASS_ON; } } stream_filter_register("append", "append_filter")
With that (and the existing documentation about php_user_filter
class), one should be able to do all sorts of magic userland stream filtering by combining all these powerful possibilities into even stronger code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With