I've just comitted the streams filter API to CVS. The concept is that a series of filters can be applied to a given stream instance (as a doubly-linked-list); writing to the stream causes the buffer to pass through the filters from head to tail, before finally being written to the underlying stream.
Likewise, reading from the stream also causes the data to pass through the filters, but in the reverse order. Filters can be attached to either the head or tail of the filter "stack" for a stream using: php_stream_filter_prepend(stream, filter); php_stream_filter_append(stream, filter); Filters are registered in a similar way as wrappers using: php_stream_filter_register_factory(char *filterpattern, php_stream_filter_factory *factory); The factory "object" has a method which instantiates the actual filter. Filter names must have the form "<group>.<name>"; the filterpattern parameter is either the fully-qualified name of the filter to create (such as "string.rot13"), or it can be a wildcard such as "mbstring.*". Wildcard factories can be used when there are a large number of possible filters, to prevent a zillion lines that just call the register factory function; I forsee filter names such as "mbstring.sjis/utf-8" being possible (which might convert sjis into utf-8) - the filter would match an "mbstring.*" factory which would parse the "sjis/utf-8" part of the filter name and create the appropriate mbfilter converters for the returned filter object. Filters are created using: php_stream_filter_create(char *filtername, char *filterparams, int filterparamslen, int persistent TSRMLS_DC); filtername corresponds to the name used to register the factory; if no exact match is found, it looks for a factory matching "<group>.*", and then passes the filtername to it's factory method. Filterparams is a binary safe string parameter which can carry some kind of options to a filter implementation. For example, it might specifit the compression level for a zlib filter, or it might control the number of bits used in a cypher, or some kind of key for cypto filters. The filter API doesn't touch this parameter; it's interpreted only by the factory object/method. I've added two user-space functions to demonstrate filters in action; they combine the filter creation and stacking into a single function: bool stream_filter_prepend(resource stream, string filtername [, string filterparams]); bool stream_filter_append(resource stream, string filtername [, string filterparams]); Also for the sake of demonstraton, I've implemented a rot13 filter. (I chose rot13 because it did not require the filter to buffer data, and because the nice 3-line implementation of the string function happened to be near the bottom of ext/standard/strings.c :-). This code snippet opens a stream and then applies a rot13 filter: $fp = fopen("file.txt", "r"); stream_filter_prepend($fp, "string.rot13"); // Each byte that is read undergoes a rot13 tranformation before // being returned in $line. $line = fgets($fp, 1024); Once other filters have been written it would be possible to do something like this: stream_filter_prepend($fp, "compress.zlib"); stream_filter_prepend($fp, "string.toupper"); // file content is decompressed by zlib, and then // has all characters upper-cased. // Then it is output to browser. fpassthru($fp); // If we were to write to $fp instead, the inverse would happen: // characters would be lower-cased (*) and then the // data would be compressed and then written to the file *: Assuming the the inverse operation for the string.toupper filter actually lower-cased the text. It might just pass-thru the text unchanged - it's a filter specific thing. This similar code produces different results (because the filters are in a different order): stream_filter_append($fp, "compress.zlib"); stream_filter_append($fp, "string.toupper"); // data is upper cased and then decompressed. // This is probably not what is desired here fpassthru($fp); Possible Gotchas when using filters: ==================================== o Seeking is probably not a hot idea. I've tried to allow for it in the API, but when you have a filter where the number of bytes for the encoded data is not a 1:1 mapping with the number of bytes of decoded data, you could end up with problems; at the end of the day, if depends on how many layers of filters you are using and how well they handle things. o Casting a filtered stream to a FILE* will only work if your system has fopencookie. Casting to fd's or sockets will not work at all. This means that you can't (portably) pass a filtered stream to a third-party library. I think the C API is just about right (there are more functions than I've briefly mentioned here, so you can do things like remove a given filter from a stream). The user-space API might benefit from allowing and array to specify a series of filters (and parameters) all in one function call. All comments/questions are welcome, but please revisit an earlier discussion on this in the archives for a bit more background first; I can't recall the date, but I do recall that Andrei had some comments, and there was some talk of monikers too. (So search for something like "streams filter moniker andrei") --Wez. -- Wez Furlong The Brain Room Ltd. -- PHP Development Mailing List <http://www.php.net/> To unsubscribe, visit: http://www.php.net/unsub.php