PHP Streams Below the Surface

A given stream instance "knows," for example, that it's a file stream as opposed to a network stream based on the ops element of the php_stream record returned by one of the stream creation functions you used last chapter:

typedef struct _php_stream { ... php_stream_ops *ops; ... } php_stream;

The php_stream_ops struct, in turn, is defined as a collection of method pointers and a descriptive label:

typedef struct _php_stream_ops { size_t (*write)(php_stream *stream, const char *buf, size_t count TSRMLS_DC); size_t (*read)(php_stream *stream, char *buf, size_t count TSRMLS_DC); int (*close)(php_stream *stream, int close_handle TSRMLS_DC); int (*flush)(php_stream *stream TSRMLS_DC); const char *label; int (*seek)(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC); int (*cast)(php_stream *stream, int castas, void **ret TSRMLS_DC); int (*stat)(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC); int (*set_option)(php_stream *stream, int option,int value, void *ptrparam TSRMLS_DC); } php_stream_ops;

When a stream access method such as php_stream_read() is called, the streams layer actually resolves the corresponding method in the stream->ops structure to call that stream type's specific read implementation function. For example, the implementation of the read function in the plainfiles stream ops structure looks like a slightly more complex version of the following:

size_t php_stdio_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) { php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract; return read(data->fd, buf, count); }

Whereas compress.zlib streams use an ops struct that points at something roughly along the lines of this read method:

size_t php_zlib_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) { struct php_gz_stream_data_t *data = (struct php_gz_stream_data_t *) stream->abstract; return gzread(data->gz_file, buf, count); }

The first thing to notice here is that the method referenced by the ops structure's function pointer often only has to serve as a thin proxy around the underlying data source's true read method. In the case of these two examples, stdio streams find their way to the posix read() function, whereas zlib streams are routed into a call to libz's gzread() method.

You probably also noticed the stream->abstract element being used. This is a convenience pointer that stream implementations can use to carry around any relevant bound information. In these cases, pointers to custom structures are used to store the file descriptor used by the underlying read function.

One more thing you might have noticed is that each of the methods in the php_stream_ops structure expect an existing stream instance, but how does a given stream get instantiated? How does that abstract element get populated and when is a stream instructed what ops structure it will be using? The answer lies in the name of the first method you used to open a stream last chapter: php_stream_open_wrapper().

When this method is called, the PHP streams layer attempts to determine what protocol is being requested based on the scheme:// designation used in the passed URL. From there it looks up the corresponding php_stream_wrapper entry in PHP's wrapper registry. Each php_stream_wrapper structure, in turn, carries its own ops element pointing at a php_stream_wrapper_ops struct with the following type definition:

typedef struct _php_stream_wrapper_ops { php_stream *(*stream_opener)(php_stream_wrapper *wrapper, char *filename, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC); int (*stream_closer)(php_stream_wrapper *wrapper, php_stream *stream TSRMLS_DC); int (*stream_stat)(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC); int (*url_stat)(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC); php_stream *(*dir_opener)(php_stream_wrapper *wrapper, char *filename, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC); const char *label; int (*unlink)(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC); int (*rename)(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC); int (*stream_mkdir)(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC); int (*stream_rmdir)(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC); } php_stream_wrapper_ops;

From here, the streams layer calls into wrapper->ops->stream_opener(), which performs the wrapper-specific operations to create a stream instance, assign the appropriate php_stream_ops structure, and bind any relevant abstract data.

The dir_opener() method serves the same basic purpose as stream_opener(); however, it's called in response to an API call to php_stream_opendir(), and typically binds a different php_stream_ops struct to the returned instance. The stat() and close() methods are duplicated at this layer in order to allow the wrapper to add protocolspecific logic to these operations.

The remaining methods allow static stream operations to be performed without actually creating a stream instance. Recall that their streams API calls don't actually return a php_stream object. You'll see them in more detail in just a moment.

Note

Although url_stat existed internally as a wrapper ops method when the streams layer was introduced in PHP 4.3, it was not used by the core until PHP 5.0. In addition, the last three methods, rename(), stream_mkdir(), and stream_rmdir(), were not introduced until PHP 5.0 and thus are not part of the wrapper op structure until this version.

Категории