Advanced PHP Programming
.pdf
548 Chapter 21 Extending PHP: Part I
Jim Winstead gives a regular (and evolving) talk on extension writing, titled “Hacking the PHP Source.” A recent copy of the slides is available at http://talks.php.net/ show/hacking-fall-2003.
The Spread client wrapper extension is available in the PECL extension library, at http://pecl.php.net/spread.
550 Chapter 22 Extending PHP: Part II
HashTable *static_members;
HashTable constants_table;
struct _zend_function_entry *builtin_functions;
union _zend_function *constructor; union _zend_function *destructor; union _zend_function *clone; union _zend_function *_ _get; union _zend_function *_ _set; union _zend_function *_ _call;
zend_class_iterator_funcs iterator_funcs;
/* handlers */
zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC); zend_object_iterator *(*get_iterator)
(zend_class_entry *ce, zval *object TSRMLS_DC); int (*interface_gets_implemented)
(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); zend_class_entry **interfaces;
zend_uint num_interfaces;
char *filename; zend_uint line_start; zend_uint line_end; char *doc_comment;
zend_uint doc_comment_len;
};
This is not small. Fortunately, there are macros to help you with most of it. Note the following:
nThe struct contains hashtables for all methods, constants, static properties, and default property values.
nAlthough it has a private hashtable for methods, it has separate zend_function slots for its constructor, destructor, clone, and overload handlers.
Creating a New Class
To create an empty class like this:
class Empty {}
requires only a few steps. First, in the main scope of the extension, you declare a zend_class_entry pointer that you will register your class into:
static zend_class_entry *empty_ce_ptr;
Implementing Classes |
551 |
Then, in your MINIT handler, you use the INIT_CLASS_ENTRY() macro to initialize the class and the zend_register_internal_class() function to complete the registration:
PHP_MINIT_FUNCTION(cart)
{
zend_class_entry empty_ce; INIT_CLASS_ENTRY(empty_ce, “Empty”, NULL);
empty_ce_ptr = zend_register_internal_class(&empty_ce);
}
empty_ce is used here as a placeholder to initialize class data before handing it off to zend_register_internal_function(), which handles the registration of the class into the global class table, initialization of properties and constructors, and so on. INIT_CLASS_ENTRY() takes the placeholder zend_class_entry (which, as you saw in Chapter 21, is a nontrivial data structure), and initializes all its attributes to standard default values.The second parameter to INIT_CLASS_ENTRY() is the name of the class being registered.The third parameter to INIT_CLASS_ENTRY(), which is being passed here as NULL, is the method table for the class.
empty_ce_ptr is useful because it is a live pointer to the class entry for the class that is sitting in the global function table. Normally to access a class, you would need to look it up by name in this global hashtable. By keeping a static pointer to it in the extension, you can save yourself that lookup.
When you use zend_register_internal_class(), the engine knows that the class is supposed to be persistent, meaning that like functions, they will only be loaded into the global class table once, when the server starts.
Of course, a class without any properties or methods is neither very interesting nor very useful.The first thing you need to add to a class is properties.
Adding Properties to a Class
Instance properties in PHP classes are either dynamic properties (belonging only to a particular object) or default properties (belonging to the class). Default instance properties are not static properties. Every instance has its own copy of default class properties, but every instance is guaranteed to have a copy. Dynamic instance properties are properties that are not declared in a class definition but are instead created on-the-fly after an object has been created.
Dynamic instance variables are commonly defined in a class’s constructor, like this:
class example {
public function _ _constructor()
{
$this->instanceProp = ‘default’;
}
}
552 Chapter 22 Extending PHP: Part II
PHP 5 allows for dynamic creation of instance variables such as these, but this type of variable creation is largely for backward compatibility with PHP 4.There are two major problems with dynamic instance properties:
n Because they are not part of the class entry, they cannot be inherited.
nBecause they are not part of the class entry, they are not visible through the reflection API.
The preferred PHP 5 method is to declare the variable in the class definition, like this:
class example {
public $instanceProp = ‘default’;
}
In PHP 4 it is standard to create all extension class properties as dynamic instance properties, usually in the class constructor. In PHP 5, extension classes should look more like PHP classes (at least in their public interface).This means you need to be able to create an extension class HasProperties that looks like the following.
class HasProperties {
public $public_property = ‘default’; public $unitialized_property; protected $protected_property; private $private_property;
}
Furthermore, this class should behave as a regular PHP class when it comes to inheritance and PPP. Of course, there is a set of helper functions for handling all this:
zend_declare_property(zend_class_entry *ce, char *name, int name_length, zval *property, int access_type TSRMLS_DC);
zend_declare_property_null(zend_class_entry *ce, char *name, int name_length, int access_type TSRMLS_DC);
zend_declare_property_long(zend_class_entry *ce, char *name, int name_length, long value, int access_type TSRMLS_DC);
zend_declare_property_string(zend_class_entry *ce, char *name, int name_length, char *value, int access_type TSRMLS_DC);
ce is the class you are registering the property into. name is the name of the property you are registering. name_length is the length of name. access_type is a flag that determines the access properties for the property.The following are the property setting mask bits:
mask ZEND_ACC_STATIC ZEND_ACC_ABSTRACT ZEND_ACC_FINAL
Implementing Classes |
553 |
ZEND_ACC_INTERFACE
ZEND_ACC_PUBLIC
ZEND_ACC_PROTECTED
ZEND_ACC_PRIVATE
To use a property declaration function, you call it immediately after class registration.The following is a C implementation of HasProperties:
Note
Note that for clarity I’ve separated the class registration code into a helper function that is called from
PHP_MINIT_FUNCTION(). Cleanliness and compartmentalization are essential to code maintainability.
static zend_class_entry *has_props_ptr;
void register_HasProperties(TSRMLS_D)
{
zend_class_entry ce; zval *tmp;
INIT_CLASS_ENTRY(ce, “HasProperties”, NULL);
has_props_ptr = zend_register_internal_class(&ce TSRMLS_CC);
zend_declare_property_string(has_props_ptr,
“public_property”, strlen(“public_property”),
“default”, ACC_PUBLIC);
zend_declare_property_null(has_props_ptr,
zend_declare_property_null(has_props_ptr, “uninitialized_property”, strlen(“uninitialized_property”), ACC_PUBLIC);
zend_declare_property_null(has_props_ptr, “protected_property”, strlen(“protected_property”), ACC_PROTECTED);
zend_declare_property_null(has_props_ptr, “private_property”, strlen(“private_property”), ACC_PRIVATE);
}
PHP_MINIT_FUNCTION(example)
{
register_HasProperties(TSRMLS_CC);
}
554 Chapter 22 Extending PHP: Part II
Class Inheritance
To register a class as inheriting from another class, you should use the following function:
zend_class_entry *zend_register_internal_class_ex(zend_class_entry *class_entry,
zend_class_entry *parent_ce,
char *parent_name TSRMLS_DC);
class_entry is the class you are registering.The class you are inheriting from is specified by passing either a pointer to its zend_class_entry structure (parent_ce) or by passing the parent class’s name, parent_name. For example, if you want to create a class ExampleException that extends Exception, you could use the following code:
static zend_class_entry *example_exception_ptr;
void register_ExampleException(TSRMLS_DC)
{
zend_class_entry *ee_ce;
zend_class_entry *exception_ce = zend_exception_get_default(); INIT_CLASS_ENTRY(ee_ce, “ExampleException”, NULL); example_exception_ptr =
zend_register_internal_class_ex(ee_ce, exception_ce, NULL TSRMLS_CC);
}
PHP_MINIT_FUNCTION(example)
{
register_ExampleException(TSEMLS_CC);
}
This code example is almost identical to the class registration example presented earlier in this chapter, in the section “Creating a New Class,” with one critical difference. In this code, you pass a pointer to the Exception class zend_class_entry structure (obtained via zend_exception_get_default()) as the second parameter to zend_register_ internal_class_ex(). Because you know the class entry, you do not need to pass in
parent_name.
Private Properties
Although it may not yet be fully clear, defining private properties in classes is a bit silly. Because private properties cannot be accessed from outside the class or by derived classes, they really are purely for internal use. Therefore, it would make more sense to have your private variables be structs of native C types. You’ll soon see how to accomplish this.
Implementing Classes |
555 |
Adding Methods to a Class
After adding properties to a class, the next thing you most likely want to do is add methods. As you know from programming PHP, class methods are little more than functions. The little more part is that they have a class as their calling context, and (if they are not static methods) they have the object they are acting on passed into them. In extension code, the paradigm stays largely the same. Extension class methods are represented by a zend_function type internally and are declared with the ZEND_METHOD() macro.
To gain access to the calling object ($this), you use the function getThis(), which returns a zval pointer to the object handle.
To assist in finding properties internally, the Zend API provides the following accessor function:
zval *zend_read_property(zend_class_entry *scope, zval *object, char *name, int name_length, zend_bool silent TSRMLS_DC);
This function looks up the property named name in object of class scope and returns its associated zval. silent specifies whether an undefined property warning should be emitted if the property does not exist.
The standard way of using this function is as follows:
zval *data, *obj;
obj = getThis();
data = zend_read_property(Z_OBJCE_P(obj), obj, “property”, strlen(“property”), 1 TSRMLS_CC);
Although it is possible to access the property’s hashtable directly via looking at Z_OBJPROP_P(obj), you almost never want to do this. zend_read_property() correctly handles inherited properties, automatic munging of private and protected properties, and custom accessor functions.
Similarly, you do not want to directly update an object’s properties hashtable, but instead should use one of the zend_update_property() functions.The simplest update function is the following:
void zend_update_property(zend_class_entry *scope, zval *object, char *name, int name_length, zval *value TSRMLS_DC);
This function updates the property name in the object object in class scope to be value. Like array values, there are convenience functions for setting property values from base C data types. Here is a list of these convenience functions:
void zend_update_property_null(zend_class_entry *scope, zval *object, char *name, int name_length TSRMLS_DC);
void zend_update_property_long(zend_class_entry *scope, zval *object, char *name, int name_length,
long value TSRMLS_DC);
void zend_update_property_string(zend_class_entry *scope, zval *object, char *name, int name_length,
char *value TSRMLS_DC);
556 Chapter 22 Extending PHP: Part II
These functions work identically to the zend_declare_property() functions presented in the previous section.
To see how this works, consider the following PHP code taken from the classic object-orientation example in the PHP manual:
class Cart { public $items;
function num_items()
{
return count($this->items);
}
}
Assuming that Cart has already been defined in the extension, num_items() would be written as follows:
PHP_FUNCTION(cart_numitems)
{
zval *object; zval *items;
HashTable *items_ht;
object = getThis();
items = zend_read_property(Z_OBJCE_P(object), object, “items”, strlen(“items”), 1 TSRMLS_CC),
if(items) {
if(items_ht = HASH_OF(items)) { RETURN_LONG(zend_hash_num_elements(items_ht));
}
}
RETURN_FALSE;
}
To register this in your class, you define a table of methods called cart_methods and then pass that into INIT_CLASS_ENTRY() when you initialize Cart:
static zend_class_entry *cart_ce_ptr;
static zend_function_entry cart_methods[] = { ZEND_ME(cart, numitems, NULL, ZEND_ACC_PUBLIC)
{NULL, NULL, NULL}
};
void register_cart()
{
Implementing Classes |
557 |
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, “Cart”, cart_methods);
cart_ce_ptr = zend_register_internal_class(*ce TSRMLS_CC); zend_declare_property_null(has_props_ptr, “items”,
strlen(“items”), ACC_PUBLIC);
}
PHP_MINIT_FUNCTION(cart)
{
register_cart();
}
Note that the zend_function_entry array looks a bit different than it did before. Instead of PHP_FE(cart_numitems, NULL), you have ZEND_ME(cart, numitems, NULL, ZEND_ACC_PUBLIC).This allows you to register the function defined by
ZEND_METHOD(cart, numitems) as the public method numitems in the class cart.This is useful because it handles all the name munging necessary to avoid function naming conflicts while allowing the method and class names to appear clean.
Adding Constructors to a Class
Special cases for method names are the constructor, destructor, and clone functions. As in userspace PHP, these functions should be registered with the names _ _construct,
_ _destruct, and _ _clone, respectively.
Other than this, there is nothing particularly special about a constructor, destructor, or clone function. is the following constructor for Cart allows an object to be passed in:
class Cart { public $items;
public function _ _construct($item)
{
$this->items[] = $item;
}
/* ... */
}
In C, this constructor is as follows:
ZEND_METHOD(cart, _ _construct)
{
zval *object; zval *items; zval *item;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “z”, &item) == FAILURE) { return;
}
