Despite being a heavily unified API, there are actually four distinct paths to opening a stream depending on the type of stream required. Looking at it from a userspace perspective, the four categories are differentiated roughly as follows (function lists are representative samples, not comprehensive listings):
No matter which type of stream you'll be opening, they are all stored in a single common structure: php_stream.
Fopen Wrappers
Let's start by simply re-implementing the fopen() function and proceed from there. By now you should be accustomed to creating an extension skeleton; if not, refer back to Chapter 5, "Your First Extension," for the basic structure:
PHP_FUNCTION(sample5_fopen) { php_stream *stream; char *path, *mode; int path_len, mode_len; int options = ENFORCE_SAFE_MODE | REPORT_ERRORS; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &path, &path_len, &mode, &mode_len) == FAILURE) { return; } stream = php_stream_open_wrapper(path, mode, options, NULL); if (!stream) { RETURN_FALSE; } php_stream_to_zval(stream, return_value); }
The purpose of php_stream_open_wrapper() should be pretty clear right off the bat. path specifies a filename or URL to be opened for reading, writing, or both depending on the value of mode.
options is a set of zero or more flag bits, in this case set to a fixed pair of values described here:
USE_PATH |
Relative paths will be applied to the locations specified in the .ini option include_path. This option is specified by the built-in fopen() function when the third parameter is passed as TRUE. |
STREAM_USE_URL |
When set, only remote URLs will be opened. Wrappers that are not flagged as remote URLs such as file://, php://, compress.zlib://, and compress.bzip2:// will result in failure. |
ENFORCE_SAFE_MODE |
Despite the naming of this constant, safe mode checks are only truly enforced if this option is set, and the corresponding safe_mode ini directive has been enabled. Excluding this option causes safe_mode checks to be skipped regardless of the INI setting. |
REPORT_ERRORS |
If an error is encountered during the opening of the specified resource, an error will only be generated if this flag is passed. |
STREAM_MUST_SEEK |
Some streams, such as socket transports, are never seekable; others, such as file handles, are only seekable under certain circumstances. If a calling scope specifies this option and the wrapper determines that it cannot guarantee seekability, it will refuse to open the stream. |
STREAM_WILL_CAST |
If the calling scope will require the stream to be castable to a stdio or posix file descriptor, it should pass this option to the open_wrapper function so that it can fail gracefully before I/O operations have begun. |
STREAM_ONLY_GET_HEADERS |
Indicates that only metadata will be requested from the stream. In practice this is used by the http wrapper to populate the http_response_headers global variable without actually fetching the contents of the remote file. |
STREAM_DISABLE_OPEN_BASEDIR |
Like the safe_mode check, this option, even when absent, still requires the open_basedir ini option to be enabled for checks to be performed. Specifying it as an option simply allows the default check to be bypassed. |
STREAM_OPEN_PERSISTENT |
Instructs the streams layer to allocate all internal structures persistently and register the associated resource in the persistent list. |
IGNORE_PATH |
If not specified, the default include path will be searched. Most URL wrappers ignore this option. |
IGNORE_URL |
When provided, only local files will be opened by the streams layer. All is_url wrappers will be ignored. |
The final NULL parameter could have been a char** that will be initially set to match path and, if the path points to a plainfiles URL, updated to exclude the file:// portion, leaving a simple filepath to be used by traditional filename operations. This parameter is traditionally used by internal engine processes only.
An extended version of php_stream_open_wrapper() also exists:
php_stream *php_stream_open_wrapper_ex(char *path, char *mode, int options, char **opened_path, php_stream_context *context);
This last parameter, context, allows for additional control of, and notification from, the wrapper in use. You'll see this parameter in action in Chapter 16.
Transports
Although transport streams are made up of the same component parts as fopen wrapper streams, they're given their own scheme registry and kept apart from the rest of the crowd. In part, this is because of the difference in how they've been traditionally accessed from userspace; however, there are additional implementation factors that are only relevant to socket-based streams.
From your perspective as an extension developer, the process of opening transports is just the same. Take a look at this re-creation of fsockopen():
PHP_FUNCTION(sample5_fsockopen) { php_stream *stream; char *host, *transport, *errstr = NULL; int host_len, transport_len, implicit_tcp = 1, errcode = 0; long port = 0; int options = ENFORCE_SAFE_MODE; int flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &host, &host_len, &port) == FAILURE) { return; } if (port) { int implicit_tcp = 1; if (strstr(host, "://")) { /* A protocol was specified, * no need to fall back on tcp:// */ implicit_tcp = 0; } transport_len = spprintf(&transport, 0, "%s%s:%d", implicit_tcp ? "tcp://" : "", host, port); } else { /* When port isn't specified * we can safely assume that a protocol was * (e.g. unix:// or udg://) */ transport = host; transport_len = host_len; } stream = php_stream_xport_create(transport, transport_len, options, flags, NULL, NULL, NULL, &errstr, &errcode); if (transport != host) { efree(transport); } if (errstr) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%d] %s", errcode, errstr); efree(errstr); } if (!stream) { RETURN_FALSE; } php_stream_to_zval(stream, return_value); }
The basic mechanics of this function are the same. All that has changed is that host and port, being specified in different parameters, must be joined together in order to generate a transport URI. After a meaningful "path" is generated, it's passed into the xport_create() function in the same way as fopen() used the open_wrapper() API call. The full prototype for php_stream_xport_create() is described here:
php_stream *php_stream_xport_create(char *xport, int xport_len, int options, int flags, const char *persistent_id, struct timeval *timeout, php_stream_context *context, char **errstr, int *errcode);
The meaning of each of these parameters is as follows:
xport |
URI-based transport descriptor. For inet socket-based streams this might be tcp://127.0.0.1:80, udp://10.0.0.1:53, or ssl://169.254.13.24:445. Reasonable values might also be unix:///path/to/socket or udg:///path/to/dgramsocket for UNIX transports. The xport_len allows xport to specify a binary safe value by explicitly naming the length of the transport string. |
options |
This value is made up of a bitwise OR'd combination of the same values used by php_stream_open_wrapper() documented earlier in this chapter. |
flags |
Also a bitwise OR'd combination of either STREAM_XPORT_CLIENT or STREAM_XPORT_SERVER combined with any number of the remaining STREAM_XPORT_* constants defined in the next table. |
persistent_id |
If this transport should persist between requests, the calling scope can provide a keyname to describe the connection. Specifying this value as NULL creates a non-persistent connection; specifying a unique string value will attempt to recover an existing transport from the persistent pool, or create a new persistent stream if one does not exist yet. |
timeout |
How long a connection attempt should block before timing out and returning failure. A value of NULL passed here will use the default timeout as specified in the php.ini. This parameter has no meaning for server transports. |
errstr |
If an error occurs while creating, connecting, binding, or listening for the selected transport, the char* value passed by reference here will be populated with a descriptive string reporting the cause of the failure. The value of errstr should initially point to NULL; if it is populated with a value on return, the calling scope is responsible for freeing the memory associated with this string. |
errcode |
A numeric error code corresponding to the error message returned via errstr. |
The STREAM_XPORT_* family of constantsfor use in the flags parameter to php_stream_xport_create()are as follows:
STREAM_XPORT_CLIENT |
The local end will be establishing a connection to a remote resource via the transport. This flag is usually accompanied by STREAM_XPORT_CONNECT or STREAM_XPORT_CONNECT_ASYNC. |
STREAM_XPORT_SERVER |
The local end will accept connections from a remote client via the transport. This flag is usually accompanied by STREAM_XPORT_BIND, and often STREAM_XPORT_LISTEN as well. |
STREAM_XPORT_CONNECT |
A connection to the remote resource should be established as part of the transport creation process. Omitting this flag when creating a client transport is legal, but requires a separate call to php_stream_xport_connect() in this case. |
STREAM_XPORT_CONNECT_ASYNC |
Attempt to connect to the remote resource, but do not block. |
STREAM_XPORT_BIND |
Bind the transport to a local resource. When used with server transports this prepares the transport for accepting connections on a particular port, path, or other specific endpoint identifier. |
STREAM_XPORT_LISTEN |
Listen for inbound connections on the bound transport endpoint. This is typically used with stream-based transports such as tcp://, ssl://, and unix://. |
Directory Access
For fopen wrappers that support directory access, such as file:// and ftp://, a third stream opener function can be used as in this re-creation of opendir():
PHP_FUNCTION(sample5_opendir) { php_stream *stream; char *path; int path_len, options = ENFORCE_SAFE_MODE | REPORT_ERRORS; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &path, &path_len) == FAILURE) { return; } stream = php_stream_opendir(path, options, NULL); if (!stream) { RETURN_FALSE; } php_stream_to_zval(stream, return_value); }
Once again, a stream is being opened for a particular path description that may be a simple directory name on the local filesystem, or a URL-formatted resource describing a wrapper that supports directory access. We find the options parameter again, which has its usual meaning, and a third parameterset to NULL herefor passing a php_stream_context.
After the directory stream is open, it's passed out to userspace just like any other file or transport stream.
Special Streams
A few more specialized stream types exist that don't fit cleanly within the fopen/transport/directory molds. Each of these are generated by their own unique API calls:
php_stream *php_stream_fopen_tmpfile(void); php_stream *php_stream_fopen_temporary_file(const char *dir, const char *pfx, char **opened_path);
Create a seekable buffer stream that can be written to and read from. Upon closing, any resources temporarily in use by this stream, including all buffers whether in memory or on disk, will be released. Using the latter function in this pair allows the temporary file to be spooled to a specific location with a specifically formatted name. These internal API calls are shadowed by the userspace tmpfile() function.
php_stream *php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id); php_stream *php_stream_fopen_from_file(FILE *file, const char *mode); php_stream *php_stream_fopen_from_pipe(FILE *file, const char *mode);
These three API methods take an already opened FILE* resource or file descriptor ID and wrap it in the appropriate stream operations for use with the Streams API. The fd form will not search for a matching persistent id like the earlier fopen methods you're familiar with, but it will register the produced stream as persistent for later opening.
The PHP Life Cycle
Variables from the Inside Out
Memory Management
Setting Up a Build Environment
Your First Extension
Returning Values
Accepting Parameters
Working with Arrays and HashTables
The Resource Data Type
PHP4 Objects
PHP5 Objects
Startup, Shutdown, and a Few Points in Between
INI Settings
Accessing Streams
Implementing Streams
Diverting the Stream
Configuration and Linking
Extension Generators
Setting Up a Host Environment
Advanced Embedding
Appendix A. A Zend API Reference
Appendix B. PHPAPI
Appendix C. Extending and Embedding Cookbook
Appendix D. Additional Resources