The Resource type: zend_resource¶
Even though PHP could really get rid of the “resource” type, because custom object storage allows to build a PHP representation of any abstract kind of data, that resource type still exists in the current version of PHP, and you may need to deal with it.
If you need to create resources, we really would like to push you not to, but instead use objects and their custom storage management. Objects is the PHP type that can embed anything of any type. However, for historical reasons, PHP still knows about that special type “Resource”, and still makes use of it in its heart or in some extensions. Let’s see that type together. Beware however, it is really cryptic and suffers from a long past history, so don’t be surprised about its design especially when reading the source code about it
What is the “Resource” type?¶
Easy enough you know about it. We are talking about this here:
$fp = fopen('/proc/cpuinfo', 'r');
var_dump($fp); /* resource(2) of type (stream) */
Internally, a resource is bound to the zend_resource
structure type:
struct _zend_resource {
zend_refcounted_h gc;
int handle;
int type;
void *ptr;
};
We find the traditional zend_refcounted_h
header, meaning that resources are reference countable.
The handle
is an integer that is used internally by the engine to locate the resource into an internal resource
table. It is used as the key for such a table.
The type
is used to regroup resources of the same type together. This is about the way resources get destroyed and
how they are fetched back from their handle.
Finally, the ptr
field in zend_resource
is your abstract data. Remember resources are about storing an abstract
data that cannot fit in any data type PHP can represent natively (but objects could, like we said earlier).
Resource types and resource destruction¶
Resources must register a destructor. When users use resources in PHP userland, they usually don’t bother cleaning
those when they don’t make use of them anymore. For example, it is not uncommon to see an fopen()
call, and not see
the matching fclose()
call. Using the C language, this would be at best a bad idea, at most a disaster. But using a
high level language like PHP, you ease things.
You, as an internal developer, must be prepared to the fact that the user would create a lot of resources you’ll allow him to use, without properly cleaning them and releasing memory/OS resource. You hence must register a destructor that will be called anytime the engine is about to destroy a resource of that type.
Destructors are grouped by types, so are resources themselves. You won’t apply the destructor for a resource of type ‘database’ than for a resource of type ‘file’.
There also exists two kinds of resources, here again differentiated about their lifetime.
Classical resources, the most used ones, do not persist across several requests, their destructor is called at request shutdown.
Persistent resources will persist across several requests and will only get destroyed when the PHP process dies.
Note
You may be interested by the PHP lifecycle chapter that shows you the different steps occurring in PHP’s process life. Also, the Zend Memory Manager chapter may help in understanding concepts of persistent and request-bound memory allocations.
Playing with resources¶
The resources related API can be found in zend/zend_list.c. You may find some inconsistencies into it, like talking about “lists” for “resources”.
Creating resources¶
To create a resource, one must first register a destructor for it and associate it to a resource type name using
zend_register_list_destructors_ex()
. That call will return an integer that represents the type of resource you
register. You must remember that integer because you will need it later-on to fetch back your resource from the user.
After that, you can register a new resource using zend_register_resource()
. That one will return you a
zend_resource
. Let’s see together a simple use-case example:
#include <stdio.h>
int res_num;
FILE *fp;
zend_resource *my_res;
zval my_val;
static void my_res_dtor(zend_resource *rsrc)
{
fclose((FILE *)rsrc->ptr);
}
/* module_number should be your PHP extension number here */
res_num = zend_register_list_destructors_ex(my_res_dtor, NULL, "my_res", module_number);
fp = fopen("/proc/cpuinfo", "r");
my_res = zend_register_resource((void *)fp, res_num);
ZVAL_RES(&my_val, my_res);
What we do in the code above, is that we open a file using libc’s fopen()
, and store the returned pointer into a
resource. Before that, we registered a destructor which when called will use libc’s fclose()
on the pointer. Then,
we register the resource against the engine, and we pass the resource into a zval
container that could get returned
to userland.
Note
Zvals chapter can be found here.
What must be remembered is resource type. Here, we register a resource of type “my_res”. This is the type name. The
engine does not really care about type name, but type identifier, the integer returned by
zend_register_list_destructors_ex()
. You should remember it somewhere, like we do in the res_num
variable.
Fetching back resources¶
Now that we registered a resource and put it in a zval
for an example, we should learn how to fetch back that
resource from the userland. Remember, the resource is stored into the zval
. Into the resource is stored the resource
type number (on the type
field). Thus, to be given back our resource from the user, we must extract the
zend_resource
from the zval
, and call zend_fetch_resource()
to get back our FILE *
pointer:
/* ... later on ... */
zval *user_zval = /* fetch zval from userland, assume type IS_RESOURCE */
ZEND_ASSERT(Z_TYPE_P(user_zval) == IS_RESOURCE); /* just a check to be sure */
fp = (FILE *)zend_fetch_resource(Z_RESVAL_P(user_zval), "my_res", res_num);
Like we said : get back a zval from the user (of type IS_RESOURCE
), and fetch the resource pointer back from it by
calling zend_fetch_resource()
.
That function will check if the type of the resource is of the type you pass as third parameter (res_num
here).
If yes, it extracts back the void *
resource pointer you need and we are done. If not, then it throws a warning like
“supplied resource is not a valid {type name} resource”.
This could happen if for example you expect a resource of type “my_res”, and you are given a zval with a resource of
type “gzip”, like one returned by gzopen()
PHP function.
Resource types are just a way for the engine to mix different kind of resources (of type “file”, “gzip” or even “mysql
connection”) into the same resource table. Resource types have names, so that those can be used in error messages or in
debug statement (like a var_dump($my_resource)
), and they also are represented as an integer used internally to
fetch back the resource pointer from it, and to register a destructor with the resource type.
Note
Like you can see, if we would have used objects, those represent types by themselves, and there wouldn’t have to happen that step of fetching back a resource from its identifier verifying its type. Objects are self-describing types. But resources are still a valid data type for the current PHP version.
Reference counting resources¶
Like many other types, zend_resource
is reference counted. We can see its zend_refcounted_h
header. Here is the
API to play with reference counting, if you need it (you shouldn’t really need it on an average):
zend_list_delete(zend_resource *res)
decrements refcount and destroys resource if drops to zerozend_list_free(zend_resource *res)
checks if refcount is zero, and destroys the resource if true.zend_list_close(zend_resource *res)
calls the resource destructor whatever the conditions
Persistent resources¶
Persistent resources don’t get destroyed at the end of the request. The classical use-case for that are persistent database connections. Those are connections that are recycled from request to request (with all the bullshit that will bring).
Traditionally, you should not be using persistent resources, as one request will be different from the other. Reusing the same resource should really be thoughtful before going this way.
To register a persistent resource, use a persistent destructor instead of a classical one. This is done in the call
to zend_register_list_destructors_ex()
, which API is like:
zend_register_list_destructors_ex(rsrc_dtor_func_t destructor, rsrc_dtor_func_t persistent_destructor,
const char *type_name, int module_number);