Introduction
Resources
Initializing Resources
Accepting Resources as Function Parameters
Destroying Resources
Destroying a Resource by Force
Persistent Resources
Finding Existing Persistent Resources
Sanity Check
Summary
Introduction
Up until now, you’ve worked with concepts that are familiar and map easily to userspace analogies. In this tutorial, you’ll dig into the inner workings of a more alien data type – completely opaque in userspace, but with behavior that should ultimately inspire a sense of déjà vu.
Resources
While a PHP zval
can represent a wide range of internal data types, one data type that is impossible to represent fully within a script is the pointer. Representing a pointer as a value becomes even more difficult when the structure your pointer references is an opaque typedef
. Since there’s no meaningful way to present these complex structures, there’s also no way to act upon them meaningfully using traditional operators. The solution to this problem is to simply refer to the pointer by an essentially arbitrary label called a resource.
In order for the resource’s label to have any kind of meaning to the Zend Engine, its underlying data type must first be registered with PHP. You’ll start out by defining a simple data structure in php_hello.h. You can place it pretty much anywhere but, for the sake of this exercise, put it after the#define
statements, and before the PHP_MINIT_FUNCTION
declaration. You’re also defining a constant, which will be used for the resource’s name as shown during a call to var_dump()
.
|
Now, open up hello.c and add a true global integer before yourZEND_DECLARE_MODULE_GLOBALS
statement:
|
List entry identifiers (le_*
) are one of the very few places where you’ll declare true, honest to goodness global variables within a PHP extension. These values are simply used with a lookup table to associate resource types with their textual names and their destructor methods, so there’s nothing about them that needs to be threadsafe. Your extension will generate a unique number for each resource type it exports during the MINIT
phase. Add that to your extension now, by placing the following line at the top ofPHP_MINIT_FUNCTION(hello)
:
|
Initializing Resources
Now that you’ve registered your resource, you need to do something with it. Add the following function to hello.c, along with its matching entry in thehello_functions
structure, and as a prototype in php_hello.h:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &name, &name_len, &age) == FAILURE) { if (name_len < 1) { if (age < 0 || age > 255) { person = emalloc(sizeof(php_hello_person)); |
Before allocating memory and duplicating data, this function performs a few sanity checks on the data passed into the resource: Was a name provided? Is this person’s age even remotely within the realm of a human lifespan? Of course, anti-senescence research could make the data type for age (and its sanity-checked limits) seem like the Y2K bug someday, but it’s probably safe to assume no-one will be older than 255 anytime soon.
Once the function has satisfied its entrance criteria, it’s all down to allocating some memory and putting the data where it belongs. Lastly,return_value
is populated with a newly registered resource. This function doesn’t need to understand the internals of the data struct; it only needs to know what its pointer address is, and what resource type that data is associated with.
Accepting Resources as Function Parameters
From the previous tutorial in this series, you already know how to usezend_parse_parameters()
to accept a resource parameter. Now it’s time to apply that to recovering the data that goes with a given resource. Add this next function to your extension:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zperson) == FAILURE) { ZEND_FETCH_RESOURCE(person, php_hello_person*, &zperson, -1, PHP_HELLO_PERSON_RES_NAME, le_hello_person); php_printf("Hello "); |
The important parts of the functionality here should be easy enough to parse.ZEND_FETCH_RESOURCE()
wants a variable to drop the pointer value into. It also wants to know what the variable’s internal type should look like, and it needs to know where to get the resource identifier from.
The -1
in this function call is an alternative to using &zperson
to identify the resource. If any numeric value is provided here other than -1
, the Zend Engine will attempt to use that number to identify the resource rather than the zval*
parameter’s data. If the resource passed does not match the resource type specified by the last parameter, an error will be generated using the resource name given in the second to last parameter.
There is more than one way to skin a resource though. In fact the following four code blocks are all effectively identical:
ZEND_FETCH_RESOURCE(person, php_hello_person *, NULL, Z_LVAL_P(zperson), PHP_HELLO_PERSON_RES_NAME, le_person_name); person = (php_hello_person *) zend_fetch_resource(&zperson TSRMLS_CC, -1, PHP_HELLO_PERSON_RES_NAME, NULL, 1, le_person_name); |
The last couple of forms are useful in situations where you’re not in aPHP_FUNCTION()
, and therefore have no return_value
to assign; or when it’s perfectly reasonable for the resource type to not match, and simply returningFALSE
is not what you want.
However you choose to retrieve your resource data from the parameter, the result is the same. You now have a familiar C struct that can be accessed in exactly the same way as you would any other C program. At this point the struct still ‘belongs’ to the resource variable, so your function shouldn’t free the pointer or change reference counts prior to exiting. So how are resources destroyed?
Destroying Resources
Most PHP functions that create resource parameters have matching functions to free those resources. For example, mysql_connect()
has mysql_close()
,mysql_query()
has mysql_free_result()
, fopen()
has fclose()
, and so on and so forth. Experience has probably taught you that if you simply unset()
variables containing resource values, then whatever real resource they’re attached to will also be freed/closed. For example:
$fp = fopen('foo.txt','w'); |
The first line of this snippet opens a file for writing, foo.txt, and assigns the stream resource to the variable $fp
. When the second line unsets $fp
, PHP automatically closes the file – even though fclose()
was never called. How does it do that?
The secret lies in the zend_register_resource()
call you made in your MINIT
function. The two NULL
parameters you passed correspond to cleanup (or dtor
) functions. The first is called for ordinary resources, and the second for persistent ones. We’ll focus on ordinary resources for now and come back to persistent resources later on, but the general semantics are the same. Modify your zend_register_resource
line as follows:
|
and create a new function located just above the MINIT
method:
|
As you can see, this simply frees any allocated buffers associated with the resource. When the last userspace variable containing a reference to your resource goes out of scope, this function will be automatically called so that your extension can free memory, disconnect from remote hosts, or perform other last minute cleanup.
Destroying a Resource by Force
If calling a resource’s dtor
function depends on all the variables pointing to it going out of scope, then how do functions like fclose()
ormysql_free_result()
manage to perform their job while references to the resource still exist? Before I answer that question, I’d like you to try out the following:
$fp = fopen('test', 'w'); var_dump($fp); |
In both calls to var_dump()
, you can see the numeric value of the resource number, so you know that a reference to your resource still exists; yet the second call to var_dump()
claims the type is ‘unknown’. This is because the resource lookup table which the Zend Engine keeps in memory, no longer contains the file handle to match that number – so any attempt to perform aZEND_FETCH_RESOURCE()
using that number will fail.
fclose()
, like so many other resource-based functions, accomplishes this by using zend_list_delete()
. Perhaps obviously, perhaps not, this function deletes an item from a list, specifically a resource list. The simplest use of this would be:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zperson) == FAILURE) { |
Of course, this function will destroy *any* resource type regardless of whether it’s our person
resource, a file handle, a MySQL connection, or whatever. In order to avoid causing potential trouble for other extensions and making userspace code harder to debug, it is considered good practice to first verify the resource type. This can be done easily by fetching the resource into a dummy variable using ZEND_FETCH_RESOURCE()
. Go ahead and add that to your function, between the zend_parse_parameters()
call andzend_list_delete()
.
Persistent Resources
If you’ve used mysql_pconnect()
, popen()
or any of the other persistent resource types, then you’ll know that it’s possible for a resource to stick around, not just after all the variables referencing it have gone out of scope, but even after a request completes and a new one begins. These resources are called persistent resources, because they persist throughout the life of the SAPI unless deliberately destroyed.
The two key differences between standard resources and persistent ones are the placement of the dtor
function when registering, and the use ofpemalloc()
rather than emalloc()
for data allocation.
Let’s build a version of our the person
resource that can remain persistent. Start by adding another zend_register_resource()
line to MINIT
. Don’t forget to define the le_hello_person_persist
variable next to le_hello_person
:
|
The basic syntax is the same, but this time you’ve specified the destructor function in the second parameter to zend_register_resource()
as opposed to the first. All that really distinguishes one of these from the other is when the dtor
function is actually called. A dtor
function passed in the first parameter is called with the active request shutdown, while a dtor
function passed in the second parameter isn’t called until the module is unloaded during final shutdown.
Since you’ve referenced a new resource dtor
function, you’ll need to define it. Adding this familiar looking method to hello.c somewhere above the MINIT
function should do the trick:
|
Now you need a way to instantiate a persistent version of the person
resource. The established convention is to create a new function with a ‘p’ prefix in the name. Add this function to your extension:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &name, &name_len, &age) == FAILURE) { if (name_len < 1) { if (age < 0 || age > 255) { person = pemalloc(sizeof(php_hello_person), 1); |
As you can see, this function differs only slightly from hello_person_new()
. In practice, you’ll typically see these kinds of paired userspace functions implemented as wrapper functions around a common core. Take a look through the source at other paired resource creation functions to see how this kind of duplication is avoided.
Now that your extension is creating both types of resources, it needs to be able to handle both types. Fortunately, ZEND_FETCH_RESOURCE
has a sister function that is up to the task. Replace your current call toZEND_FETCH_RESOURCE
in hello_person_greet()
with the following:
|
This will load your person
variable with appropriate data, regardless of whether or not a persistent resource was passed.
The functions these two FETCH
macros call will actually allow you to specify any number of resource types, but it’s rare to need more than two. Just in case, here’s the last statement rewritten using the base function:
|
There are two important things to notice here. Firstly, you can see that theFETCH_RESOURCE
macros automatically attempt to verify the resource. Expanded out, the ZEND_VERIFY_RESOURCE
macro in this case simply translates to:
|
Of course, you don’t always want your extension function to exit just because a resource couldn’t be fetched, so you can use the realzend_fetch_resource()
function to try to fetch the resource type, but then use your own logic to deal with NULL
values being returned.
Finding Existing Persistent Resources
A persistent resource is only as good as your ability to reuse it. In order to reuse it, you’ll need somewhere safe to store it. The Zend Engine provides for this through the EG(persistent_list)
executor global, aHashTable
containing list_entry
structures which is normally used internally by the Eengine. Modify hello_person_pnew()
according to the following:
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &name, &name_len, &age) == FAILURE) { if (name_len < 1) { if (age < 0 || age > 255) { /* Look for an established resource */ /* New person, allocate a structure */ ZEND_REGISTER_RESOURCE(return_value, person, le_hello_person_persist); /* Store a reference in the persistence list */ |
This version of hello_person_pnew()
first checks for an existingphp_hello_person
structure in the EG(persistent_list)
global and, if available, uses that rather than waste time and resources on reallocating it. If it does not exist yet, the function allocates a new structure populated with fresh data and adds that structure to the persistent list instead. Either way, the function leaves you with a new structure registered as a resource within the request.
The persistent list used to store pointers is always local to the current process or thread, so there’s never any concern that two requests might be looking at the same data at the same time. If one process deliberately closes a persistent resource PHP will handle it, removing the reference to that resource from the persistent list so that future invocations don’t try to use the freed data.
Sanity Check
Once again, your extension files as they should be by the end of this tutorial:
config.m4
|
php_hello.h
#ifdef ZTS ZEND_BEGIN_MODULE_GLOBALS(hello) #ifdef ZTS #define PHP_HELLO_WORLD_VERSION "1.0" typedef struct _php_hello_person { #define PHP_HELLO_PERSON_RES_NAME "Person Data" PHP_MINIT_FUNCTION(hello); PHP_FUNCTION(hello_world); extern zend_module_entry hello_module_entry; |
hello.c
#include "php.h" int le_hello_person; ZEND_DECLARE_MODULE_GLOBALS(hello) static function_entry hello_functions[] = { zend_module_entry hello_module_entry = { #ifdef COMPILE_DL_HELLO PHP_INI_BEGIN() static void php_hello_init_globals(zend_hello_globals *hello_globals) PHP_RINIT_FUNCTION(hello) return SUCCESS; static void php_hello_person_persist_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) if (person) { static void php_hello_person_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) if (person) { PHP_MINIT_FUNCTION(hello) ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL); REGISTER_INI_ENTRIES(); return SUCCESS; PHP_MSHUTDOWN_FUNCTION(hello) return SUCCESS; PHP_FUNCTION(hello_world) PHP_FUNCTION(hello_long) RETURN_LONG(HELLO_G(counter)); PHP_FUNCTION(hello_double) PHP_FUNCTION(hello_bool) PHP_FUNCTION(hello_null) PHP_FUNCTION(hello_greetme) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zname) == FAILURE) { convert_to_string(zname); php_printf("Hello "); RETURN_TRUE; PHP_FUNCTION(hello_add) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ld|b", &a, &b, &return_long) == FAILURE) { if (return_long) { PHP_FUNCTION(hello_dump) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &uservar) == FAILURE) { switch (Z_TYPE_P(uservar)) { RETURN_TRUE; PHP_FUNCTION(hello_array) array_init(return_value); add_index_long(return_value, 42, 123); ALLOC_INIT_ZVAL(mysubarray); add_next_index_string(mysubarray, "hello", 1); PHP_FUNCTION(hello_array_strings) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) == FAILURE) { arr_hash = Z_ARRVAL_P(arr); php_printf("The array passed contains %d elements for(zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer)) { zval temp; if (zend_hash_get_current_key_ex(arr_hash, &key, &key_len, &index, 0, &pointer) == HASH_KEY_IS_STRING) { php_printf(" => "); temp = **data; RETURN_TRUE; static int php_hello_array_walk(zval **element TSRMLS_DC) return ZEND_HASH_APPLY_KEEP; static int php_hello_array_walk_arg(zval **element, char *greeting TSRMLS_DC) return ZEND_HASH_APPLY_KEEP; static int php_hello_array_walk_args(zval **element, int num_args, va_list args, zend_hash_key *hash_key) php_printf("%s", prefix); return ZEND_HASH_APPLY_KEEP; PHP_FUNCTION(hello_array_walk) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &zarray) == FAILURE) { zend_hash_apply(Z_ARRVAL_P(zarray), (apply_func_t)php_hello_array_walk TSRMLS_CC); RETURN_TRUE; PHP_FUNCTION(hello_array_value) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "az", &zarray, &zoffset) == FAILURE) { switch (Z_TYPE_P(zoffset)) { if (key && zend_hash_find(Z_ARRVAL_P(zarray), key, key_len + 1, (void**)&zvalue) == FAILURE) { *return_value = **zvalue; PHP_FUNCTION(hello_get_global_var) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &varname, &varname_len) == FAILURE) { if (zend_hash_find(&EG(symbol_table), varname, varname_len + 1, (void**)&varvalue) == FAILURE) { *return_value = **varvalue; PHP_FUNCTION(hello_set_local_var) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &varname, &varname_len, &value) == FAILURE) { ALLOC_INIT_ZVAL(newvar); zend_hash_add(EG(active_symbol_table), varname, varname_len + 1, &newvar, sizeof(zval*), NULL); RETURN_TRUE; PHP_FUNCTION(hello_person_new) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &name, &name_len, &age) == FAILURE) { if (name_len < 1) { if (age < 0 || age > 255) { person = emalloc(sizeof(php_hello_person)); ZEND_REGISTER_RESOURCE(return_value, person, le_hello_person); PHP_FUNCTION(hello_person_pnew) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &name, &name_len, &age) == FAILURE) { if (name_len < 1) { if (age < 0 || age > 255) { /* Look for an established resource */ /* New person, allocate a structure */ ZEND_REGISTER_RESOURCE(return_value, person, le_hello_person_persist); /* Store a reference in the persistence list */ efree(key); PHP_FUNCTION(hello_person_greet) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zperson) == FAILURE) { ZEND_FETCH_RESOURCE2(person, php_hello_person*, &zperson, -1, PHP_HELLO_PERSON_RES_NAME, le_hello_person, le_hello_person_persist); php_printf("Hello "); RETURN_TRUE; PHP_FUNCTION(hello_person_delete) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zperson) == FAILURE) { |
Summary
In this tutorial, Part Three of the Extension Writing series, you learned the few simple steps necessary to attach arbitrary, sometimes opaque data to PHP userspace variables. In later steps, you’ll combine these techniques with linking against third party libraries to create the kind of glue extensions you see so frequently with PHP.
In Part Four, we’ll take a look into Objects – from the simple arrays-with-functions available in PHP 4, to the more complex overloaded OOP structures available in PHP 5.
Copyright © Sara Golemon, 2006. All rights reserved.