Contexts
Every stream context contains two intrinsic types of information. The first, and most commonly used, is the context option. These values, arranged into a two-level nested array within contexts, are typically used to change how a stream wrapper initializes. The other type, context parameters, are meant to be wrapper agnostic and currently provide a means for event notification within the streams layer to bubble up to a piece of streamsusing code.
php_stream_context *php_stream_context_alloc(void);
Creating a context uses this simple API call, which allocates some storage space and initializes the HashTables that will hold the context's options and parameters. It is also automatically registered as a resource and is therefore implicitly cleaned up on request shutdown.
Setting Options
The internal API for setting context options shadows the userspace APIs almost identically:
int php_stream_context_set_option(php_stream_context *context, const char *wrappername, const char *optionname, zval *optionvalue);
All that really differs from the userspace proto:
bool stream_context_set_option(resource $context, string $wrapper, string $optionname, mixed $value);
is the specific data types, which differ between userspace and internals out of necessity. As an example, a piece of internals code might use the two API calls just covered to make an HTTP request using the built-in wrapper, while overriding the user_agent setting with a context option.
php_stream *php_sample6_get_homepage( const char *alt_user_agent) { php_stream_context *context; zval tmpval; context = php_stream_context_alloc(); ZVAL_STRING(&tmpval, alt_user_agent, 0); php_stream_context_set_option(context, "http", "user_agent", &tmpval); return php_stream_open_wrapper_ex("http://www.php.net", "rb", REPORT_ERRORS | ENFORCE_SAFE_MODE, NULL, context); }
Note
Notice that tmpval wasn't allocated any permanent storage, and the string it was populated with wasn't duplicated. php_stream_context_set_option automatically makes a duplicate of both the passed zval and all of its contents.
Retrieving Options
The API call to retrieve a context option mirrors its setting counterpart with an extra hint of déjàvu.
int php_stream_context_get_option(php_stream_context *context, const char *wrappername, const char *optionname, zval ***optionvalue);
Recall that context options are stored in a set of nested HashTables and that when retrieving values from a HashTable, the normal approach is to pass a pointer to a zval** into zend_hash_find(). Well, because php_stream_context_get_option() is a specialized proxy for zend_hash_find(), it only stands to reason that the semantics would be the same.
Here's a simplified look at one of the built-in http wrapper's uses of php_stream_context_get_option showing how the user_agent setting is applied to a specific request:
zval **ua_zval; char *user_agent = "PHP/5.1.0"; if (context && php_stream_context_get_option(context, "http", "user_agent", &ua_zval) == SUCCESS && Z_TYPE_PP(ua_zval) == IS_STRING) { user_agent = Z_STRVAL_PP(ua_zval); }
In this case, non-string values are simply thrown out because it doesn't make sense to use a number for a user agent string. Other context options, such as max_redirects, do take numeric values, and because it's not uncommon to find a numeric value stored in a string zval, it might be necessary to perform a type conversion to use the otherwise legitimate setting.
Unfortunately, these variables are owned by the context so they can't be simply converted immediately; instead they must be separatedas you did in prior chaptersand then converted, and finally destroyed if necessary:
long max_redirects = 20; zval **tmpzval; if (context && php_stream_context_get_option(context, "http", "max_redirects", &tmpzval) == SUCCESS) { if (Z_TYPE_PP(tmpzval) == IS_LONG) { max_redirects = Z_LVAL_PP(tmpzval); } else { zval copyval = **tmpzval; zval_copy_ctor(©val); convert_to_long(©val); max_redirects = Z_LVAL(copyval); zval_dtor(©val); } }
Note
In practice, the zval_dtor() in this example would not be necessary. IS_LONG variables do not use any additional storage beyond the zval container itself and thus a zval_dtor() is a non-op. It's included in this example for completeness as it is necessaryand vitalfor String, Array, Object, Resource, and potentially other data types in the future.
Parameters
Although the userspace API presents context parameters as a unified looking construct similar to context options, they are actually declared as independent members of the php_stream_context struct within the language internals.
At present, only one context parameter is supported: notifier. This element of the php_stream_context struct can optionally point to a php_stream_notifier struct that has the following members:
typedef struct { php_stream_notification_func func; void (*dtor)(php_stream_notifier *notifier); void *ptr; int mask; size_t progress, progress_max; } php_stream_notifier;
When a php_stream_notifier struct is assigned to context->notifier, it providesat minimuma callback func that is triggered on special stream events shown in Table 16.1 as PHP_STREAM_NOTIFY_* codes. A given event will also bear one of the PHP_STREAM_NOTIFY_SEVERITY_* levels shown in Table 16.2.
PHP_STREAM_NOTIFY_* Codes |
Meaning |
---|---|
RESOLVE |
A host address resolution has completed. Most socket-based wrappers perform this lookup just prior to connection. |
CONNECT |
A socket stream connection to a remote resource has completed. |
AUTH_REQUIRED |
The requested resource is unavailable due to access controls and insufficient authorization. |
MIME_TYPE_IS |
The mime-type of the remote resource is now available. |
FILE_SIZE_IS |
The size of the remote resource is now available. |
REDIRECTED |
The original URL request resulted in a redirect to another location. |
PROGRESS |
The progress and (possibly) progress_max elements of the php_stream_notifier struct have been updated as a result of addition data having been transferred. |
COMPLETED |
There is no more data available on the stream. |
FAILURE |
The URL resource request was unsuccessful or could not complete. |
AUTH_RESULT |
The remote system has processed authentication credentialspossibly successfully. |
PHP_STREAM_NOTIFY_SEVERITY_* Levels |
Meaning |
---|---|
INFO |
Informational update. Equivalent to an E_NOTICE error. |
WARN |
Minor error condition. Equivalent to an E_WARNING error. |
ERR |
Sever error condition. Equivalent to an E_ERROR error. |
A convenience pointer *ptr is provided for notifier implementations to carry around additional data. If that pointer refers to space that must be freed when the context is destructed, a dtor method may be specified and will be called when the last reference to the context falls out of scope.
The mask element allows event triggers to be limited to specific severity levels. If an event occurs at a severity level not included in mask, the notifier function will not be triggered.
The last two elementsprogress and progress_maxcan be populated by the stream implementation; however, notifier functions should avoid using either of these values until they have received at least one PHP_STREAM_NOTIFY_PROGRESS or PHP_STREAM_NOTIFY_FILE_SIZE_IS event respectively.
The following example conforms to the prototype for the php_stream_notification_func callback:
void php_sample6_notifier(php_stream_context *context, int notifycode, int severity, char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void *ptr TSRMLS_DC) { if (notifycode != PHP_STREAM_NOTIFY_FAILURE) { /* Ignore all other notifications */ return; } if (severity == PHP_STREAM_NOTIFY_SEVERITY_ERR) { /* Dispatch to crisis handler */ php_sample6_theskyisfalling(context, xcode, xmsg); return; } else if (severity == PHP_STREAM_NOTIFY_SEVERITY_WARN) { /* Log the potential problem */ php_sample6_logstrangeevent(context, xcode, xmsg); return; } }
The Default Context
As of PHP 5.0, when a userspace stream creation function is called without a context parameter, the requestwide default context is used instead. This context variable is stored in the File Globals structure as FG(default_context) and may be accessed identically to any other php_stream_context variable. When performing stream creation for a userspace script, it's generally preferable to allow the user to specify a context or at least fall back on the default context. Decoding a userspace zval* into a php_stream_context can be accomplished by using the php_stream_context_from_zval() macro as in the following example adapted from Chapter 14, "Accessing Streams":
PHP_FUNCTION(sample6_fopen) { php_stream *stream; char *path, *mode; int path_len, mode_len; int options = ENFORCE_SAFE_MODE | REPORT_ERRORS; zend_bool use_include_path = 0; zval *zcontext = NULL; php_stream_context *context; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|br", &path, &path_len, &mode, &mode_len, &use_include_path, &zcontext) == FAILURE) { return; } context = php_stream_context_from_zval(zcontext, 0); if (use_include_path) { options |= PHP_FILE_USE_INCLUDE_PATH; } stream = php_stream_open_wrapper_ex(path, mode, options, NULL, context); if (!stream) { RETURN_FALSE; } php_stream_to_zval(stream, return_value); }
If zcontext contains a userspace context resource, its associated pointer will be populated into context as with any ZEND_FETCH_RESOURCE() call. On the other hand, if zcontext is NULL and the second parameter to php_stream_context_from_zval() is set to a nonzero value, the result of the macro will simply be NULL. When set to zeroas in this example and nearly all the core stream creation userspace functionsthe value of FG(default_context) will be used (and initialized if appropriate) instead.