Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced PHP Programming

.pdf
Скачиваний:
72
Добавлен:
14.04.2015
Размер:
8 Мб
Скачать
☆

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.

FLY TEAM

22

Extending PHP: Part II

NOW THAT YOU’VE MASTERED THE BASICS of extension authoring, this chapter covers advanced extension features. In this chapter you will see how to write classes and objects in extensions, how to write custom session handlers, and how to use the streams API.

Implementing Classes

By far the largest change from PHP 4 to PHP 5 is the new object model. Mirroring this, the biggest change from PHP 4 extensions to PHP 5 extensions is handling classes and objects.The procedural extension code you learned in Chapter 21,“Extending PHP: Part I,” is almost entirely backward-portable to PHP 4.The use of macros for many of the functions helps things: Macros allow for internal reimplementation without invalidating extension code. Class code, however, is substantially different in PHP 5 than in PHP 4. Not only have internal Zend Engine structures changed, but the basic semantics of classes have changed as well.This means that although certain parts of writing classes remain the same, many are completely different.

To create a new class, you must first create and register its zend_class_entry data type. A zend_class_entry struct looks like this:

struct _zend_class_entry { char type;

char *name;

zend_uint name_length;

struct _zend_class_entry *parent; int refcount;

zend_bool constants_updated; zend_uint ce_flags;

HashTable function_table;

HashTable default_properties;

HashTable properties_info;

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;

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]