
Advanced PHP Programming
.pdf
558 Chapter 22 Extending PHP: Part II
object = getThis();
MAKE_STD_ZVAL(items);
array_init(items);
add_next_index_zval(items, item); zend_declare_property(Z_OBJCE_P(object), object, “items”, strlen(“items”),
items, ZEND_ACC_PUBLIC TSRMLS_CC);
}
To register this function, you only need to add it to the cart_methods array, as follows:
static zend_function_entry cart_methods[] = { ZEND_ME(cart, _ _construct, NULL, ZEND_ACC_PUBLIC), ZEND_ME(cart, numitems, NULL, ZEND_ACC_PUBLIC),
{NULL, NULL, NULL}
}; PHP_MINIT(cart)
{
}
Throwing Exceptions
As part of a robust error-handling scheme, you need to be able to throw exceptions from extensions.There is considerable debate among PHP developers concerning whether throwing exceptions in extensions is a good idea. Most of the arguments are centered around whether it is okay to force developers into a certain coding paradigm. Most of the extensions you will write will be for your own internal use. Exceptions are an incredibly powerful tool, and if you are fond of using them in PHP code, you should not shy away from them in extension code.
Throwing an exception that derives from the base Exception class is extremely easy. The best way to do so is to use the following helper function:
void zend_throw_exception(zend_class_entry *exception_ce, char *message, long code TSRMLS_DC);
To use this function, you supply a class via exception_ce, a message via message, and a code via code.The following code throws an Exception object:
zend_throw_exception(zend_exception_get_default(), “This is a test”, 1 TSRMLS_CC);
There is also a convenience function to allow string formatting of the exception’s message:
void zend_throw_exception_ex(zend_class_entry *exception_ce,
long code TSRMLS_DC, char *format, ...);

Implementing Classes |
559 |
Note that code is now in the first position, while the message parameter to zend_throw_exception() is replaced with fmt and a variable number of parameters. Here is a single line of code to throw an exception that contains the file and line number of the C source file where the exception was created:
zend_throw_exception_ex(zend_exception_get_default(), 1,
“Exception at %s:%d”, _ _FILE_ _, _ _LINE_ _);
To throw a class other than Exception, you just need to replace the zend_class_entry pointer in object_init_ex with one of your own creation.
To throw an exception that does not derive from Exception, you must create an object by hand and explicitly set EG(exception) to the object.
Using Custom Objects and Private Variables
I mentioned earlier in this chapter that storing private instance properties in the object’s properties table is silly. Because the information is to be used only internally, and internally in an extension means that it is implemented in C, the ideal case would be for private variables to be native C types.
In PHP 5, generic objects are represented by the type zend_object and are stored in a global object store.When you call getThis(), the object handle ID stored in the calling object’s zval representation is looked up in the object store. Conveniently, this object store can store more than zend_object types: It can actually store arbitrary data structures.This is useful for two reasons:
n You can store resource data (such as database connection handles) directly in the object, without having to create and manage a resource for them.
n You can store private class variables as a C struct alongside your object.
If you want custom object types, you need to create a custom class create_object function.When you instantiate a new object, the following steps occur:
1.The raw object is created. By default this allocates and initializes an object, but with a custom creation function, arbitrary structures can be initialized.
2.The newly created structure is inserted into the object store, and its ID is returned.
3.The class constructor is called.
A creation function adheres to the following prototype:
zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC);
These are the key tasks the create_object function must attend to:
nIt must minimally create a zend_object structure.
nIt must allocate and initialize the property HashTable of the object.
nIt must store the object structure it creates in the object store, using
zend_objects_store_put().

560 Chapter 22 Extending PHP: Part II
nIt must register a destructor.
nIt must return a zend_object_value structure.
Let’s convert the Spread module from Chapter 21 without using resources and so it holds its connection handle in the object. Instead of using a standard zend_object structure, you should use an object that looks like this:
typedef struct {
mailbox mbox;
zend_object zo;
} spread_object;
If you allocate any memory inside the structure or create anything that needs to be cleaned up, you need a destructor to free it. Minimally, you need a destructor to free the actual object structures. Here is code for the simplest destructor possible:
static void spread_objects_dtor(void *object,
zend_object_handle handle TSRMLS_DC)
{
zend_objects_destroy_object(object, handle TSRMLS_CC);
}
zend_objects_destroy_object() is used to destroy the allocated object itself. You also need a clone function to specify how the object should respond if its
_ _clone() method is called. Because a custom create_object handler implies that your stored object is not of standard type, you are forced to specify both of these functions.The engine has no way to determine a reasonable default behavior. Here is the clone function for the Spread extension:
static void spread_objects_clone(void *object, void **object_clone TSRMLS_DC){ spread_object *intern = (spread_object *) object;
spread_object **intern_clone = (spread_object **) object_clone;
*intern_clone = emalloc(sizeof(spread_object)); (*intern_clone)->zo.ce = intern->zo.ce; (*intern_clone)->zo.in_get = 0; (*intern_clone)->zo.in_set = 0; ALLOC_HASHTABLE((*intern_clone)->zo.properties); (*intern_clone)->mbox = intern->mbox;
}
object_clone is the new object to be created. Note that you basically deep-copy the clone data structure:You copy the ce class entry pointer and unset in_set and in_get, signifying that there is no active overloading in the object.
Then you need to have a create_object function.This function is very similar to the clone function. It allocates a new spread_object structure and sets it.Then it stores the resulting object in the object store, along with the destructor and clone handler.

Implementing Classes |
561 |
Here is the custom object creator for the Spread extension:
zend_object_value spread_object_create(zend_class_entry *class_type TSRMLS_DC)
{
zend_object_value retval; spread_object *intern;
zend_object_handlers spread_object_handlers;
memcpy(&spread_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
intern = emalloc(sizeof(spread_object)); intern->zo.ce = class_type; intern->zo.in_get = 0; intern->zo.in_set = 0;
ALLOC_HASHTABLE(intern->zo.properties); zend_hash_init(intern->zo.properties, 0, NULL, ZVAL_PTR_DTOR, 0); retval.handle = zend_objects_store_put(intern,
spread_objects_dtor, spread_objects_clone);
retval.handlers = &spread_object_handlers; return retval;
}
Now when you register the class, you need to specify this new create_object function:
static zend_class_entry *spread_ce_ptr;
static zend_function_entry spread_methods[] = { {NULL, NULL, NULL}
};
void register_spread()
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, “Spread”, spread_methods); ce.create_object = spread_object_create;
spread_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
}
To access this raw data, you use zend_object_store_get_object() to extract the entire object from the object store, as follows:
ZEND_METHOD(spread, disconnect)
{
spread_object *sp_obj;


Implementing Classes |
563 |
ZEND_ABSTRACT_ME(class_name, method_name, argument_list);
class_name and method_name are obvious. argument_list is defined via the following macro blocks:
ZEND_BEGIN_ARG_INFO(argument_list, pass_by_ref)
ZEND_END_ARG_INFO()
This block defines argument_list and specifies whether its arguments are passed by reference. Internal to this block is an ordered list of parameters given by the following:
ZEND_ARG_INFO(pass_by_ref, name)
So to create the function entries for this PHP interface:
interface Foo {
function bar($arg1, $arg2);
function baz(&arg1);
}
you need to create both argument lists, as follows:
ZEND_BEGIN_ARG_INFO(bar_args, 0)
ZEND_ARG_INFO(0, arg1)
ZEND_ARG_INFO(0, arg2)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(baz_args, 0)
ZEND_ARG_INFO(1, arg1)
ZEND_END_ARG_INFO()
You then need to create the methods table for Foo, as follows:
zend_function_entry foo_functions[] = { ZEND_ABSTRACT_METHOD(foo, bar, bar_args) ZEND_ABSTRACT_METHOD(foo, baz, baz_args) {NULL, NULL, NULL}
};
Finally, you use zend_register_internal_interface() to register Foo, as follows:
static zend_class_entry *foo_interface;
PHP_MINIT_FUNCTION(example)
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, “Foo”, foo_functions)
foo_interface = zend_register_internal_interface(&ce TSRMLS_CC); return SUCCESS;
}
That’s all you need to do to register Foo as an interface.

564 Chapter 22 Extending PHP: Part II
Specifying that an extension class implements an interface is even simpler.The Zend API provides a single convenience function for declaring all the interfaces that the class implements:
void zend_class_implements(zend_class_entry *class_entry TSRMLS_DC,
int num_interfaces, ...);
Here class_entry is the class that implements interfaces. num_interfaces is the number of interfaces that you are implementing, and the variable argument is a list of pointers to zend_class_entry structures for the interfaces the class is implementing.
Writing Custom Session Handlers
We have already discussed the session API from a userspace level, in Chapter 14,“Session Handling.” In addition to being able to register userspace handlers for session management, you can also write them in C and register them directly by using the session extension.
This section provides a quick walkthrough of how to implement a C-based session handler, using a standard DBM file as a backing store.
The session API is extremely simple. At the most basic level, you simply need to create a session module struct (similar in concept to the zend_module_entry structure). You first create a standard extension skeleton.The extension will be named session_dbm.The session API hooks can be separately namespaced; you can call them all dbm for simplicity.
The session API hook structure is declared as follows:
#include “ext/session/php_session.h” ps_module ps_mod_dbm = {
PS_MOD(dbm)
};
The PS_MOD() macro automatically registers six functions that you need to implement:
n[PS_OPEN_FUNC(dbm)]—Opens the session back end.
n[PS_CLOSE_FUNC(dbm)]—Closes the session back end.
n[PS_READ_FUNC(dbm)]—Reads data from the session back end.
n[PS_WRITE_FUNC(dbm)]—Writes data to the session back end.
n[PS_DESTROY_FUNC(dbm)]—Destroys a current session.
n[PS_GC_FUNC(dbm)]—Handles garbage collection.
For further details on the tasks these functions perform and when they perform them, refer to the discussion of their userspace equivalents in Chapter 14.
PS_OPEN_FUNC passes in three arguments:

Writing Custom Session Handlers |
565 |
nvoid **mod_data—A generic data pointer used to hold return information.
nchar *save_path—A buffer to hold the filesystem path where session data will be
saved. If you are not using file-based sessions, this should be thought of as a generic location pointer.
n char *session_name—The name of the session.
mod_data is passed and propagated along with a session and is an ideal place to hold connection information. For this extension, you should carry the location of the DBM file and a connection pointer to it, using this data structure:
typedef struct {
DBM *conn;
char *path;
} ps_dbm;
Here is PS_OPEN_FUNC, which does not do much other than initialize a ps_dbm struct and pass it back up to the session extension in mod_data:
PS_OPEN_FUNC(dbm)
{
ps_dbm *data;
data = emalloc(sizeof(ps_dbm)); memset(data, 0, sizeof(ps_dbm));
data->path = estrndup(save_path, strlen(save_path)); *mod_data = data;
return SUCCESS;
}
PS_CLOSE_FUNC() receives a single argument:
void **mod_data;
This is the same mod_data that has existed through the request, so it contains all the relevant session information. Here is PS_CLOSE(), which closes any open DBM connections and frees the memory you allocated in PS_OPEN():
PS_CLOSE_FUNC(dbm)
{
ps_dbm *data = PS_GET_MOD_DATA();
if(data->conn) { dbm_close(data->conn); data->conn = NULL;
}
if(data->path) { efree(data->path); data->path = NULL;

566 Chapter 22 Extending PHP: Part II
}
return SUCCESS;
}
PS_READ_FUNC() takes four arguments:
n void **mod_data—The data structure passed through all the handlers.
n const char *key—The session ID.
n char **val—An out-variable passed by reference.The session data gets passed back up in this string.
n int *vallen—The length of val.
In the following code, PS_READ_FUNC() opens the DBM if it has not already been opened and fetches the entry keyed with key:
PS_READ_FUNC(dbm)
{
datum dbm_key, dbm_value;
ps_dbm *data = PS_GET_MOD_DATA(); if(!data->conn) {
if((data->conn = dbm_open(data->path, O_CREAT|O_RDWR, 0640)) == NULL) { return FAILURE;
}
}
dbm_key.dptr = (char *) key; dbm_key.dsize = strlen(key);
dbm_value = dbm_fetch(data->conn, dbm_key); if(!dbm_value.dptr) {
return FAILURE;
}
*val = estrndup(dbm_value.dptr, dbm_value.dsize); *vallen = dbm_value.dsize;
return SUCCESS;
}
datum is a GDBM/NDBM type used to store key/value pairs. Note that the read mechanism does not have to know anything at all about the type of data being passed through it; the session extension itself handles all the serialization efforts.
PS_WRITE_FUNC() is passed arguments similar to those passed to PS_READ_FUNC():
n void **mod_data—The data structure passed through all the handlers.
n const char *key—The session ID.
n const char *val—A string version of the data to be stored (the output of the serialization method used by the session extension).
n int vallen—The length of val.

Writing Custom Session Handlers |
567 |
PS_WRITE_FUNC() is almost identical to PS_READ_FUNC(), except that it inserts data instead of reading it:
PS_WRITE_FUNC(dbm)
{
datum dbm_key, dbm_value;
ps_dbm *data = PS_GET_MOD_DATA(); if(!data->conn) {
if((data->conn = dbm_open(data->path, O_CREAT|O_RDWR, 0640)) == NULL) { return FAILURE;
}
}
dbm_key.dptr = (char *)key; dbm_key.dsize = strlen(key); dbm_value.dptr = (char *)val; dbm_value.dsize = vallen;
if(dbm_store(data->conn, dbm_key, dbm_value, DBM_REPLACE) != 0) { return FAILURE;
}
return SUCCESS;
}
PS_DESTROY_FUNC() takes two arguments:
nvoid **mod_data—The data structure passed through all the handlers.
nconst char *key—The session ID to be destroyed.
The following function simply calls dbm_delete to delete the key in question:
PS_DESTROY_FUNC(dbm)
{
datum dbm_key;
ps_dbm *data = PS_GET_MOD_DATA();
if(!data->conn) {
if((data->conn = dbm_open(data->path, O_CREAT|O_RDWR, 0640)) == NULL) { return FAILURE;
}
}
dbm_key.dptr = (char *)key; dbm_key.dsize = strlen(key); if(dbm_delete(data->conn, dbm_key)) {
return FAILURE;
}
return SUCCESS;
}