15. Using OOP in our Example Extension
Let’s try to extend our example extension, implementing the following PHP class in C.
<?php
class Scaler {
const DEFAULT_FACTOR = 2;
private $factor = DEFAULT_FACTOR;
function __construct($factor = self:: DEFAULT_FACTOR) {
$this->factor = factor;
}
function scale(&$x) {
test_scale($x, $this→factor);
}
}
C-code versions of methods are written in a similar way to PHP internal functions. Just use PHP_METHOD() macro, with class and method names, instead of PHP_FUNCTION. In methods $this variable (zval) is available as ZEND_THIS macro. In method __construct(), we assign values to property $factor, using zend_update_property_long() function, and in method scale() read it, using zend_read_property().
static zend_class_entry *scaler_class_entry = NULL;
#define DEFAULT_SCALE_FACTOR 2
PHP_METHOD(Scaler, __construct)
{
zend_long factor = DEFAULT_FACTOR; // default value
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(factor)
ZEND_PARSE_PARAMETERS_END();
if (ZEND_NUM_ARGS() > 0) {
zend_update_property_long(Z_OBJCE_P(ZEND_THIS), ZEND_THIS,
“factor”, sizeof(“factor”)-1, factor);
}
}
PHP_METHOD(Scaler, scale)
{
zval *x, *zv, tmp;
zend_long factor;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL(x)
ZEND_PARSE_PARAMETERS_END();
zv = zend_read_property(Z_OBJCE_P(ZEND_THIS), ZEND_THIS,
“factor”, sizeof(“factor”)-1, 0, &tmp);
factor = zval_get_long(zv);
do_scale_ref(x, factor);
}
Argument information descriptors are created and used in exactly the same way as for regular functions. Then, information about all the class methods must be collected into single array. This is similar to a list of extension functions, but using PHP_ME() macro instead of ZEND_FE(). PHP_ME(), which takes two additional arguments. The first one is the class name, and the fourth method flags (e.g. ZEND_ ACC_PUBLIC, ZEND_ACC_PROTECTED, ZEND_ACC_PRIVATE, ZEND_ACC_STATIC).
ZEND_BEGIN_ARG_INFO(arginfo_scaler_construct, 0)
ZEND_ARG_INFO(0, factor)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_scaler_scale, 0)
ZEND_ARG_INFO(1, x) // pass by reference
ZEND_END_ARG_INFO()
static const zend_function_entry scaler_functions[] = {
PHP_ME(Scaler, __construct, arginfo_scaler_construct, ZEND_ACC_PUBLIC)
PHP_ME(Scaler, scale, arginfo_scaler_scale, ZEND_ACC_PUBLIC)
PHP_FE_END
};
Finally, we have to register the class and its entities in MINIT.
INIT_CLASS_ENTRY() initializes temporary class entry structure, sets the name of the class, and adds the given class methods. zend_ register_class_entry() registers class in the global class table and returns the resulting class entry. Then we add constant and property to the class.
PHP_MINIT_FUNCTION(test)
{
zend_class_entry ce;
REGISTER_INI_ENTRIES();
INIT_CLASS_ENTRY(ce, “Scaler”, scaler_functions);
scaler_class_entry = zend_register_internal_class(&ce);
zend_declare_class_constant_long(scaler_class_entry,
“DEFAULT_FACTOR”, sizeof(“DEFAULT_FACTOR”)-1, DEFAULT_SCALE_FACTOR);
zend_declare_property_long(scaler_class_entry,
“factor”, sizeof(“factor”)-1, DEFAULT_SCALE_FACTOR, ZEND_ACC_PRIVATE);
return SUCCESS;
}
And this works:
$ php -r ‘$o = new Scaler(5); $x = 5; $o->scale($x); var_dump($x, $o);’
int(25)
object(Scaler)#1 (1) {
[“factor”:”Scaler”:private]=>
int(5)
}
The implemented class doesn’t make a lot of sense (except of educational), because it doesn’t work better than the original class written in PHP. In practice, it makes sense to re-implement something in C, when it’s more efficient, uses less memory, or just can’t be written in PHP. For example, we may embed some C data into PHP object.