WARNING: some prototypes in this file are out of date.
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 every time 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.
Streams use a php_stream*
parameter just as ANSI stdio (fread etc.) use a FILE*
parameter.
The main functions are:
PHPAPIsize_tphp_stream_read(php_stream*stream, char*buf, size_tcount); PHPAPIsize_tphp_stream_write(php_stream*stream, constchar*buf, size_tcount); PHPAPIsize_tphp_stream_printf(php_stream*stream, constchar*fmt, ...); PHPAPIintphp_stream_eof(php_stream*stream); PHPAPIintphp_stream_getc(php_stream*stream); PHPAPIchar*php_stream_gets(php_stream*stream, char*buf, size_tmaxlen); PHPAPIintphp_stream_close(php_stream*stream); PHPAPIintphp_stream_flush(php_stream*stream); PHPAPIintphp_stream_seek(php_stream*stream, off_toffset, intwhence); PHPAPIoff_tphp_stream_tell(php_stream*stream); PHPAPIintphp_stream_lock(php_stream*stream, intmode);
These (should) behave in the same way as the ANSI stdio functions with similar names: fread, fwrite, fprintf, feof, fgetc, fgets, fclose, fflush, fseek, ftell, flock.
In most cases, you should use this API:
PHPAPIphp_stream*php_stream_open_wrapper(constchar*path, constchar*mode, intoptions, char**opened_path);
Where:
path
is the file or resource to open.mode
is the stdio compatible mode eg: "wb", "rb" etc.options
is a combination of the following values:IGNORE_PATH
(default) - don't use include path to search for the fileUSE_PATH
- use include path to search for the fileIGNORE_URL
- do not use plugin wrappersREPORT_ERRORS
- show errors in a standard format if something goes wrong.STREAM_MUST_SEEK
- If you really need to be able to seek the stream and don't need to be able to write to the original file/URL, use this option to arrange for the stream to be copied (if needed) into a stream that can be seek()ed.
opened_path
is used to return the path of the actual file opened, but if you usedSTREAM_MUST_SEEK
, may not be valid. You are responsible forefree()ing
opened_path
.opened_path
may be (and usually is)NULL
.
If you need to open a specific stream, or convert standard resources into streams there are a range of functions to do this defined in php_streams.h
. A brief list of the most commonly used functions:
PHPAPIphp_stream*php_stream_fopen_from_file(FILE*file, constchar*mode); /* Convert a FILE * into a stream. */PHPAPIphp_stream*php_stream_fopen_tmpfile(void); /* Open a FILE * with tmpfile() and convert into a stream. */PHPAPIphp_stream*php_stream_fopen_temporary_file(constchar*dir, constchar*pfx, char**opened_path); /* Generate a temporary file name and open it. */
There are some network enabled relatives in php_network.h
:
PHPAPIphp_stream*php_stream_sock_open_from_socket(intsocket, intpersistent); /* Convert a socket into a stream. */PHPAPIphp_stream*php_stream_sock_open_host(constchar*host, unsigned shortport, intsocktype, inttimeout, intpersistent); /* Open a connection to a host and return a stream. */PHPAPIphp_stream*php_stream_sock_open_unix(constchar*path, intpersistent, structtimeval*timeout); /* Open a UNIX domain socket. */
If you need to copy some data from one stream to another, you will be please to know that the streams API provides a standard way to do this:
PHPAPIsize_tphp_stream_copy_to_stream(php_stream*src, php_stream*dest, size_tmaxlen);
If you want to copy all remaining data from the src stream, pass PHP_STREAM_COPY_ALL
as the maxlen parameter, otherwise maxlen indicates the number of bytes to copy. This function will try to use mmap where available to make the copying more efficient.
If you want to read the contents of a stream into an allocated memory buffer, you should use:
PHPAPIsize_tphp_stream_copy_to_mem(php_stream*src, char**buf, size_tmaxlen, intpersistent);
This function will set buf to the address of the buffer that it allocated, which will be maxlen bytes in length, or will be the entire length of the data remaining on the stream if you set maxlen to PHP_STREAM_COPY_ALL
. The buffer is allocated using pemalloc()
. You need to call pefree()
to release the memory when you are done. As with copy_to_stream
, this function will try use mmap where it can.
If you have an existing stream and need to be able to seek()
it, you can use this function to copy the contents into a new stream that can be seek()ed
:
PHPAPIintphp_stream_make_seekable(php_stream*origstream, php_stream**newstream);
It returns one of the following values:
#definePHP_STREAM_UNCHANGED 0 /* orig stream was seekable anyway */#definePHP_STREAM_RELEASED 1 /* newstream should be used; origstream is no longer valid */#definePHP_STREAM_FAILED 2 /* an error occurred while attempting conversion */#definePHP_STREAM_CRITICAL 3 /* an error occurred; origstream is in an unknown state; you should close origstream */
make_seekable
will always set newstream to be the stream that is valid if the function succeeds. When you have finished, remember to close the stream.
NOTE: If you only need to seek forward, there is no need to call this function, as the php_stream_seek
can emulate forward seeking when the whence parameter is SEEK_CUR
.
NOTE: Writing to the stream may not affect the original source, so it only makes sense to use this for read-only use.
NOTE: If the origstream is network based, this function will block until the whole contents have been downloaded.
NOTE: Never call this function with an origstream that is referenced as a resource! It will close the origstream on success, and this can lead to a crash when the resource is later used/released.
NOTE: If you are opening a stream and need it to be seekable, use the STREAM_MUST_SEEK
option to php_stream_open_wrapper();
PHPAPIintphp_stream_supports_lock(php_stream*stream);
This function will return either 1 (success) or 0 (failure) indicating whether or not a lock can be set on this stream. Typically, you can only set locks on stdio 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, (void*)&fp, REPORT_ERRORS) ==FAILURE) { RETURN_FALSE; }
The prototype is:
PHPAPIintphp_stream_cast(php_stream*stream, intcastas, void**ret, intshow_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, database streams etc. etc.
In situations where this is not desirable, you should query the stream to see if it naturally supports FILE *
. You can use this code snippet for this purpose:
if (php_stream_is(stream, PHP_STREAM_IS_STDIO)) { /* can safely cast to FILE* with no adverse side effects */ }
You can use:
PHPAPIintphp_stream_can_cast(php_stream*stream, intcastas)
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 can be a socket */ }
Please note the difference between php_stream_is
and php_stream_can_cast
; stream_is
tells you if the stream is a particular type of stream, whereas can_cast
tells you if the stream can be forced into the form you request. The former doesn't change anything, while the later might change some state in the stream.
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 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:
PHPAPIphp_stream*php_stream_fopen(constchar*filename, constchar*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) returnret; fclose(fp); } returnNULL; }
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:
PHPAPIphp_stream*php_stream_alloc(php_stream_ops*ops, void*abstract, size_tbufsize, intpersistent, constchar*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 streamlevel
will be disabled (recommended for underlying sources that implement their own buffering - such aFILE*
)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 aw
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()
.
RULE #1: when writing your own streams: make sure you have configured PHP with
--enable-debug
. Some great great pains have been taken to hook into the Zend memory manager to help track down allocation problems. It will also help you spot incorrect use of the STREAMS_DC, STREAMS_CC and the semi-private STREAMS_REL_CC macros for function definitions.RULE #2: Please use the stdio stream as a reference; it will help you understand the semantics of the stream operations, and it will always be more up to date than these docs :-)
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:
structmy_state { MYSQLconn; MYSQL_RES*result; }; structmy_state*state=pemalloc(sizeof(structmy_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 your own php_stream_ops struct (we called it my_ops in the above example).
For example, for reading from this weird MySQL stream:
staticsize_tphp_mysqlop_read(php_stream*stream, char*buf, size_tcount) { structmy_state*state= (structmy_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 (... atendoffile ...) returnEOF; elsereturn0; } /* 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_opsmy_ops= { php_mysqlop_write, php_mysqlop_read, php_mysqlop_close, php_mysqlop_flush, NULL, NULL, NULL, "Strange MySQL example" }
That's 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.