mirror of
https://github.com/php/php-src.git
synced 2024-12-05 15:53:40 +08:00
244 lines
8.8 KiB
Plaintext
244 lines
8.8 KiB
Plaintext
|
An Overview of the PHP Streams abstraction
|
||
|
==========================================
|
||
|
$Id$
|
||
|
|
||
|
Please send comments to: Wez Furlong <wez@thebrainroom.com>
|
||
|
|
||
|
Note: this doc is preliminary and is intended to give the reader an idea of
|
||
|
how streams work and should be used.
|
||
|
|
||
|
Why Streams?
|
||
|
============
|
||
|
You may have noticed a shed-load of issock parameters flying around the PHP
|
||
|
code; we don't want them - they are ugly and cumbersome and force you to
|
||
|
special case sockets and files everytime you need to work with a "user-level"
|
||
|
PHP file pointer.
|
||
|
Streams take care of that and present the PHP extension coder with an ANSI
|
||
|
stdio-alike API that looks much nicer and can be extended to support non file
|
||
|
based data sources.
|
||
|
|
||
|
Using Streams
|
||
|
=============
|
||
|
Streams use a php_stream* parameter just as ANSI stdio (fread etc.) use a
|
||
|
FILE* parameter.
|
||
|
|
||
|
The main functions are:
|
||
|
|
||
|
PHPAPI size_t php_stream_read(php_stream * stream, char * buf, size_t count);
|
||
|
PHPAPI size_t php_stream_write(php_stream * stream, const char * buf, size_t
|
||
|
count);
|
||
|
PHPAPI int php_stream_eof(php_stream * stream);
|
||
|
PHPAPI int php_stream_getc(php_stream * stream);
|
||
|
PHPAPI char *php_stream_gets(php_stream * stream, char *buf, size_t maxlen);
|
||
|
PHPAPI int php_stream_close(php_stream * stream);
|
||
|
PHPAPI int php_stream_flush(php_stream * stream);
|
||
|
PHPAPI int php_stream_seek(php_stream * stream, off_t offset, int whence);
|
||
|
PHPAPI off_t php_stream_tell(php_stream * stream);
|
||
|
|
||
|
These (should) behave in the same way as the ANSI stdio functions with similar
|
||
|
names: fread, fwrite, feof, fgetc, fgets, fclose, fflush, fseek, ftell.
|
||
|
|
||
|
Opening Streams
|
||
|
===============
|
||
|
Ultimately, I aim to implement an fopen_wrapper-like call to do this with
|
||
|
minimum fuss.
|
||
|
Currently, mostly for testing purposes, you can use php_stream_fopen to open a
|
||
|
stream on a regular file.
|
||
|
|
||
|
PHPAPI php_stream * php_stream_fopen(const char * filename, const char *
|
||
|
mode);
|
||
|
|
||
|
This call behaves just like fopen(), except it returns a stream instead of a
|
||
|
FILE *
|
||
|
|
||
|
Casting Streams
|
||
|
===============
|
||
|
What if your extension needs to access the FILE* of a user level file pointer?
|
||
|
You need to "cast" the stream into a FILE*, and this is how you do it:
|
||
|
|
||
|
FILE * fp;
|
||
|
php_stream * stream; /* already opened */
|
||
|
|
||
|
if (php_stream_cast(stream, PHP_STREAM_AS_STDIO, &fp, 1) == FAILURE) {
|
||
|
RETURN_FALSE;
|
||
|
}
|
||
|
|
||
|
The prototype is:
|
||
|
|
||
|
PHPAPI int php_stream_cast(php_stream * stream, int castas, void ** ret, int
|
||
|
show_err);
|
||
|
|
||
|
The show_err parameter, if non-zero, will cause the function to display an
|
||
|
appropriate error message of type E_WARNING if the cast fails.
|
||
|
|
||
|
castas can be one of the following values:
|
||
|
PHP_STREAM_AS_STDIO - a stdio FILE*
|
||
|
PHP_STREAM_AS_FD - a generic file descriptor
|
||
|
PHP_STREAM_AS_SOCKETD - a socket descriptor
|
||
|
|
||
|
If you ask a socket stream for a FILE*, the abstraction will use fdopen to
|
||
|
create it for you. Be warned that doing so may cause buffered data to be lost
|
||
|
if you mix ANSI stdio calls on the FILE* with php stream calls on the stream.
|
||
|
|
||
|
If your system has the fopencookie function, php streams can synthesize a
|
||
|
FILE* on top of any stream, which is useful for SSL sockets, memory based
|
||
|
streams, data base streams etc. etc.
|
||
|
NOTE: There might be situations where this is not desireable, and we need to
|
||
|
provide a flag to inform the casting routine of this.
|
||
|
|
||
|
You can use:
|
||
|
|
||
|
PHPAPI int php_stream_can_cast(php_stream * stream, int castas)
|
||
|
|
||
|
to find out if a stream can be cast, without actually performing the cast, so
|
||
|
to check if a stream is a socket you might use:
|
||
|
|
||
|
if (php_stream_can_cast(stream, PHP_STREAM_AS_SOCKETD) == SUCCESS) {
|
||
|
/* it's a socket */
|
||
|
}
|
||
|
|
||
|
|
||
|
Stream Internals
|
||
|
================
|
||
|
|
||
|
There are two main structures associated with a stream - the php_stream
|
||
|
itself, which holds some state information (and possibly a buffer) and a
|
||
|
php_stream_ops structure, which holds the "virtual method table" for the
|
||
|
underlying implementation.
|
||
|
|
||
|
The php_streams ops struct consists of pointers to methods that implement
|
||
|
read, write, close, flush, seek, gets and cast operations. Of these, an
|
||
|
implementation need only implement write, read, close and flush. The gets
|
||
|
method is intended to be used for non-buffered streams if there is an
|
||
|
underlying method that can efficiently behave as fgets. The ops struct also
|
||
|
contains a label for the implementation that will be used when printing error
|
||
|
messages - the stdio implementation has a label of "STDIO" for example.
|
||
|
|
||
|
The idea is that a stream implementation defines a php_stream_ops struct, and
|
||
|
associates it with a php_stream using php_stream_alloc.
|
||
|
|
||
|
As an example, the php_stream_fopen() function looks like this:
|
||
|
|
||
|
PHPAPI php_stream * php_stream_fopen(const char * filename, const char * mode)
|
||
|
{
|
||
|
FILE * fp = fopen(filename, mode);
|
||
|
php_stream * ret;
|
||
|
|
||
|
if (fp) {
|
||
|
ret = php_stream_alloc(&php_stream_stdio_ops, fp, 0, 0, mode);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
fclose(fp);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
php_stream_stdio_ops is a php_stream_ops structure that can be used to handle
|
||
|
FILE* based streams.
|
||
|
|
||
|
A socket based stream would use code similar to that above to create a stream
|
||
|
to be passed back to fopen_wrapper (or it's yet to be implemented successor).
|
||
|
|
||
|
The prototype for php_stream_alloc is this:
|
||
|
|
||
|
PHPAPI php_stream * php_stream_alloc(php_stream_ops * ops, void * abstract,
|
||
|
size_t bufsize, int persistent, const char * mode)
|
||
|
|
||
|
ops is a pointer to the implementation,
|
||
|
abstract holds implementation specific data that is relevant to this instance
|
||
|
of the stream,
|
||
|
bufsize is the size of the buffer to use - if 0, then buffering at the stream
|
||
|
level will be disabled (recommended for underlying sources that implement
|
||
|
their own buffering - such a FILE*),
|
||
|
persistent controls how the memory is to be allocated - persistently so that
|
||
|
it lasts across requests, or non-persistently so that it is freed at the end
|
||
|
of a request (it uses pemalloc),
|
||
|
mode is the stdio-like mode of operation - php streams places no real meaning
|
||
|
in the mode parameter, except that it checks for a 'w' in the string when
|
||
|
attempting to write (this may change).
|
||
|
|
||
|
The mode parameter is passed on to fdopen/fopencookie when the stream is cast
|
||
|
into a FILE*, so it should be compatible with the mode parameter of fopen().
|
||
|
|
||
|
Writing your own stream implementation
|
||
|
======================================
|
||
|
|
||
|
First, you need to figure out what data you need to associate with the
|
||
|
php_stream. For example, you might need a pointer to some memory for memory
|
||
|
based streams, or if you were making a stream to read data from an RDBMS like
|
||
|
mysql, you might want to store the connection and rowset handles.
|
||
|
|
||
|
The stream has a field called abstract that you can use to hold this data.
|
||
|
If you need to store more than a single field of data, define a structure to
|
||
|
hold it, allocate it (use pemalloc with the persistent flag set
|
||
|
appropriately), and use the abstract pointer to refer to it.
|
||
|
|
||
|
For structured state you might have this:
|
||
|
|
||
|
struct my_state {
|
||
|
MYSQL conn;
|
||
|
MYSQL_RES * result;
|
||
|
};
|
||
|
|
||
|
struct my_state * state = pemalloc(sizeof(struct my_state), persistent);
|
||
|
|
||
|
/* initialize the connection, and run a query, using the fields in state to
|
||
|
* hold the results */
|
||
|
|
||
|
state->result = mysql_use_result(&state->conn);
|
||
|
|
||
|
/* now allocate the stream itself */
|
||
|
stream = php_stream_alloc(&my_ops, state, 0, persistent, "r");
|
||
|
|
||
|
/* now stream->abstract == state */
|
||
|
|
||
|
Once you have that part figured out, you can write your implementation and
|
||
|
define the your own php_stream_ops struct (we called it my_ops in the above
|
||
|
example).
|
||
|
|
||
|
For example, for reading from this wierd mysql stream:
|
||
|
|
||
|
static size_t php_mysqlop_read(php_stream * stream, char * buf, size_t count)
|
||
|
{
|
||
|
struct my_state * state = (struct my_state*)stream->abstract;
|
||
|
|
||
|
if (buf == NULL && count == 0) {
|
||
|
/* in this special case, php_streams is asking if we have reached the
|
||
|
* end of file */
|
||
|
if (... at end of file ...)
|
||
|
return EOF;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* pull out some data from the stream and put it in buf */
|
||
|
... mysql_fetch_row(state->result) ...
|
||
|
/* we could do something strange, like format the data as XML here,
|
||
|
and place that in the buf, but that brings in some complexities,
|
||
|
such as coping with a buffer size too small to hold the data,
|
||
|
so I won't even go in to how to do that here */
|
||
|
}
|
||
|
|
||
|
Implement the other operations - remember that write, read, close and flush
|
||
|
are all mandatory. The rest are optional. Declare your stream ops struct:
|
||
|
|
||
|
php_stream_ops my_ops = {
|
||
|
php_mysqlop_write, php_mysqlop_read, php_mysqlop_close,
|
||
|
php_mysqlop_flush, NULL, NULL, NULL,
|
||
|
"Strange mySQL example"
|
||
|
}
|
||
|
|
||
|
Thats it!
|
||
|
|
||
|
Take a look at the STDIO implementation in streams.c for more information
|
||
|
about how these operations work.
|
||
|
The main thing to remember is that in your close operation you need to release
|
||
|
and free the resources you allocated for the abstract field. In the case of
|
||
|
the example above, you need to use mysql_free_result on the rowset, close the
|
||
|
connection and then use pefree to dispose of the struct you allocated.
|
||
|
You may read the stream->persistent field to determine if your struct was
|
||
|
allocated in persistent mode or not.
|
||
|
|
||
|
vim:tw=78
|