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

Reply via email to