Some time ago, in earlier versions, php did not support any object-oriented programming syntax. zend Engine (ZE1) was introduced in php4, and several new features emerged, including object data types. php4 object
Some time ago, in earlier versions, php did not support any object-oriented programming syntax. zend Engine (ZE1) was introduced in php4, and several new features emerged, including object data types.
Evolution of php object types
The first object-oriented programming (OOP) supports only the semantics of object Association. in the words of a php kernel developer, "the php4 object only binds an array with some methods ". it is the php object you want to study now.
The second major version of Zend Engine (ZE2) was released in php5, introducing some new features in php OOP implementation. for example, you can use access modifiers to mark the visibility of attributes and methods outside your class definition. function overloading can be used to define custom behaviors in the internal language structure, APIs can be used between call chains of multiple classes to implement API Standardization. when you learn Chapter 5 "php5 objects", you will build your awareness of this knowledge by implementing these features in the php5 class definition.
Implementation class
Before entering the OOP world, we need to be light loaded. Therefore, please restore your expansion to the skeleton form just built in chapter 5th "your first extension.
To be independent from your original work, you can name this version sample2. put the following three files in the ext/sample2 directory of your php source code:
Config. m4
PHP_ARG_ENABLE(sample2, [Whether to enable the "sample2" extension], [ enable-sample2 Enable "sample2" extension support]) if test $PHP_SAMPLE2 != "no"; then PHP_SUBST(SAMPLE2_SHARED_LIBADD) PHP_NEW_EXTENSION(sample2, sample2.c, $ext_shared) fi
Php_saple2.h
#ifndef PHP_SAMPLE2_H /* Prevent double inclusion */ #define PHP_SAMPLE2_H /* Define Extension Properties */ #define PHP_SAMPLE2_EXTNAME "sample2" #define PHP_SAMPLE2_EXTVER "1.0" /* Import configure options when building outside of the PHP source tree */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* Include PHP Standard Header */ #include "php.h" /* Define the entry point symbol * Zend will use when loading this module */ extern zend_module_entry sample2_module_entry; #define phpext_sample2_ptr &sample2_module_entry #endif /* PHP_SAMPLE2_H */
Sample2.c
#include "php_sample2.h" static function_entry php_sample2_functions[] = { { NULL, NULL, NULL } }; PHP_MINIT_FUNCTION(sample2) { return SUCCESS; } zend_module_entry sample2_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_SAMPLE2_EXTNAME, php_sample2_functions, PHP_MINIT(sample2), NULL, /* MSHUTDOWN */ NULL, /* RINIT */ NULL, /* RSHUTDOWN */ NULL, /* MINFO */ #if ZEND_MODULE_API_NO >= 20010901 PHP_SAMPLE2_EXTVER, #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_SAMPLE2 ZEND_GET_MODULE(sample2) #endif
Now, just like in chapter 5th, you can execute phpize,./configure, and make to build your sample2.so extension module.
Your previous config. w32 modification is the same as the config. m4 configuration.
Define class entries
In a user space, define a class as follows:
There is no doubt that it is a little difficult to implement it in expansion. first, you need to define a zend_class_entry pointer in your source code file like the int le_sample_descriptor defined in the previous chapter:
zend_class_entry *php_sample2_firstclass_entry;
Now, you can initialize and register the class in the MINIT function.
PHP_MINIT_FUNCTION (sample2) {zend_class_entry ce;/* temporary variable * // * registration class */INIT_CLASS_ENTRY (ce, "Sample2_FirstClass", NULL); signature = signature (& ce TSRMLS_CC ); return SUCCESS ;}
Build this extension and test get_declared_classes (). we will see that Sample2_FirstClass is now available in the user space.
Implementation of defining methods
At this moment, you only implement a stdClass, of course it is available, but in fact you want your class to do something.
To achieve this goal, you need to go back to another knowledge point learned in chapter 5th. replace the NULL parameter passed to INIT_CLASS_ENTRY () with php_sample2_firstclass_functions and define the structure directly in the MINIT function as follows:
static function_entry php_sample2_firstclass_functions[] = { { NULL, NULL, NULL } };
Does it look familiar? Of course, this is the same as the structure of the previously defined process function. even, the method for setting this structure is similar:
PHP_NAMED_FE(method1, PHP_FN(Sample2_FirstClass_method1), NULL)
Of course, you can also choose PHP_FE (method1, NULL ). however, let's look back at chapter 5th. the expected function implementation name is zif_method1, which may conflict with other method1 () implementations in the round. to ensure the security of the function namespace, we use the class name as the method name prefix.
The format of PHP_FALIAS (method1, Sample2_FirstClass_method1, NULL) is also acceptable. However, it is not intuitive. you may have doubts when looking back at the code. "Why didn't you use PHP_FE () at the time ()? "
Now, you have attached a function list to the class definition. it is time to define some methods. in the php_sample2_firstclass_functions structure, create the following function:
PHP_FUNCTION(Sample2_FirstClass_countProps) { RETURN_LONG(zend_hash_num_elements(Z_OBJPROP_P(getThis()))); }
Correspondingly, add a PHP_NAMED_FE () entry to its function list:
static function_entry php_sample2_firstclass_functions[] = { PHP_NAMED_FE(countprops, PHP_FN(Sample2_FirstClass_countProps), NULL) { NULL, NULL, NULL } };
Note that the function names exposed to the user space are all in lowercase. to ensure that both the method and function names are case-insensitive, internal functions must be given all in lowercase.
The only new element here is getThis (). in all php versions, it will be parsed as a macro, and the expansion is this_ptr. this_ptr essentially has the same meaning as $ this in the user space object method. if no available object instance is available, for example, if the method is called statically, getThis () returns NULL.
The returned data semantics of the object method is the same as that of the process function. Both the parameter acceptance and arg_info are the same set.
PHP_FUNCTION(Sample2_FirstClass_sayHello) { char *name; int name_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) { RETURN_NULL(); } php_printf("Hello"); PHPWRITE(name, name_len); php_printf("!\nYou called an object method!\n"); RETURN_TRUE; }
Constructor
Your class constructor can be implemented in the same way as other common class methods, and its names follow the same rules. specifically, you need to name the constructor as a class name. the other two ZE1 magic methods _ sleep () and _ wakeup () can also be implemented in this way.
Inheritance
In php4, the inheritance between internal objects is incomplete and it is best to avoid using it. if you must inherit other objects, copy the following ZE1 code:
void php_sample2_inherit_from_class(zend_class_entry *ce, zend_class_entry *parent_ce) { zend_hash_merge(&ce->function_table, &parent_ce->function_table, (void (*)(void *))function_add_ref, NULL, sizeof(zval*), 0); ce->parent = parent_ce; if (!ce->handle_property_get) { ce->handle_property_get = parent_ce->handle_property_get; } if (!ce->handle_property_set) { ce->handle_property_set = parent_ce->handle_property_set; } if (!ce->handle_function_call) { ce->handle_function_call = parent_ce->handle_function_call; } if (!zend_hash_exists(&ce->function_table, ce->name, ce->name_length + 1)) { zend_function *fe; if (zend_hash_find(&parent_ce->function_table, parent_ce->name, parent_ce->name_length + 1, (void**)fe) == SUCCESS) { zend_hash_update(&ce->function_table, ce->name, ce->name_length + 1, fe, sizeof(zend_function), NULL); function_add_ref(fe); } } }
To define such a function, you can call it under zend_register_internal_class in MINIT:
INIT_CLASS_ENTRY (ce, "Sample2_FirstClass", NULL);/* assume that token is a registered zend_class_entry */token = signature (& ce TSRMLS_CC); iterator (iterator, php_sample2_ancestor );
Although this inheritance method can work, we should avoid ZE1 inheritance because it does not design the inheritance processing of internal objects. for most OOP practices in php, ZE2 and its modified object model are robust and encourage all OOP-related tasks to use them directly.
Use instances
Like other user space variables, objects are stored in the zval * container. in ZE1, zval * contains a HashTable * used to save attributes and a zend_class_entry * pointer pointing to the class definition. in ZE2, these values are replaced by a handle table and an object ID is added to the value. they are similar to resource ID usage.
Fortunately, these differences between ZE1 and ZE2 are hidden by the Z _ * () macro introduced in Chapter 2 "internal and external variables, therefore, you do not need to care about this in your extensions. in Table 10.1, two ZE1 macros are listed, which are related to non-OOP macros. they also have corresponding _ P and _ PP versions for processing level 1 or Level 2 Inter-access requests.
Create an instance
Most of the time, you do not need to create your own instance for your extension. Instead, you need to call the new keyword in the user space to create an instance and call your class constructor.
However, you may still need to create an instance. for example, in the factory method, the object_init_ex (zval * val, zend_class_entry * ce) function in ZEND_API can be used to initialize the object instance to a variable.
Note that the object_init_ex () function does not call the constructor. When an object is instantiated in an internal function, the constructor must call it manually. the following procedure functions repeat the function logic of the new keyword:
PHP_FUNCTION (sample2_new) {int argc = ZEND_NUM_ARGS (); zval *** argv = safe_emalloc (sizeof (zval **), argc, 0); zend_class_entry * ce; if (argc = 0 | zend_get_parameters_array_ex (argc, argv) = FAILURE) {efree (argv); WRONG_PARAM_COUNT ;} /* The first parameter is the class name */SEPARATE_ZVAL (argv [0]); convert_to_string (* argv [0]); /* the class name is stored in lower case */php_strtolower (Z_STRVAL_PP (argv [0]), Z_STRLEN_PP (argv [0]); if (zend_hash_find (EG (class_table ), z_STRVAL_PP (argv [0]), Z_STRLEN_PP (argv [0]) + 1, (void **) & ce) = FAILURE) {php_error_docref (NULL TSRMLS_CC, E_WARNING, "Class % s does not exist. ", Z_STRVAL_PP (argv [0]); zval_ptr_dtor (argv [0]); efree (argv); RETURN_FALSE;} object_init_ex (return_value, ce ); /* if there is a constructor, it is called. Additional parameters will be passed to the constructor */if (zend_hash_exists (& ce-> function_table, Z_STRVAL_PP (argv [0]), z_STRLEN_PP (argv [0]) + 1) {/* the object has a constructor */zval * ctor, * dummy = NULL; /* The constructor name is the class name */MAKE_STD_ZVAL (ctor); array_init (ctor); zval_add_ref (argv [0]); add_next_index_zval (ctor, * argv [0]); zval_add_ref (argv [0]); add_next_index_zval (ctor, * argv [0]); if (call_user_function_ex (& ce-> function_table, NULL, ctor, & dummy, /* do not care about returned values */argc-1, argv + 1,/* parameter */0, NULL TSRMLS_CC) = FAILURE) {php_error_docref (NULL TSRMLS_CC, E_WARNING, "Unable to call constructor");} if (dummy) {zval_ptr_dtor (& dummy);} zval_ptr_dtor (& ctor);} zval_ptr_dtor (argv [0]); efree (argv );}
Do not forget to add a reference in php_sample2_functions. it is your list of extended process functions, not the list of class methods. to use the php_strtolower () function, you need to add # include "ext/standard/php_string.h ".
This function is one of the most complex ones you have implemented at present, with several brand new features. the first is SEPARATE_ZVAL (). In fact, you have implemented this function many times. you can use zval_copy_ctor () to assign values to a temporary struct to avoid modifying the original content. however, it is a macro version encapsulation.
Php_strtolower () is used to convert the class name to lowercase, so that the php class name and function name are case-insensitive. this is only one of the many PHPAPI tool functions listed in Appendix B.
EG (class_table) is a global variable. all zend_class_entry definitions are registered in it. note that in ZE1 (php4), this HashTable stores the zend_class_entry * structure for level-1 inter-access. in ZE2 (php5), it is stored as two-level inter-access. this should not be a problem, because direct access to this HashTable is not common, but it is always beneficial to know this.
Call_user_function_ex () is part of the ZENDAPI call you will see in chapter 20th "advanced embedded. here, you will take away the first element of the zval ** parameter stack received from zend_get_parameters_ex () to pass the remaining parameters to the constructor.
The original code cannot run in the translator's environment (php-5.4.9), you need to change zend_class_entry * ce to second-level inter-access. the following shows the code that the translator tested.
PHP_FUNCTION (sample_new) {int argc = ZEND_NUM_ARGS (); zval *** argv = safe_emalloc (sizeof (zval **), argc, 0); zend_class_entry ** ce; /* Note: Here the translator's environment (php-5.4.9) is a second-level inter-access * // * array mode to read all input parameters */if (argc = 0 | zend_get_parameters_array_ex (argc, argv) = FAILURE) {efree (argv); WRONG_PARAM_COUNT;}/* isolate the first parameter (to ensure that the following type conversion does not affect the original data) */SEPARATE_ZVAL (argv [0]);/* convert the first parameter to the string type and lowercase (because php class names are case insensitive) */convert_to_string (* argv [0]); php_strtolower (Z_STRVAL_PP (argv [0]), Z_STRLEN_PP (argv [0]); /* check whether the provided class exists in the HashTable of the class. if yes, the corresponding zend_class_entry will be obtained in ce **/if (zend_hash_find (EG (class_table ), z_STRVAL_PP (argv [0]), Z_STRLEN_PP (argv [0]) + 1, (void **) & ce) = FAILURE) {php_error_docref (NULL TSRMLS_CC, E_WARNING, "Class % s does not exist. ", Z_STRVAL_PP (argv [0]); zval_ptr_dtor (argv [0]); efree (argv); RETURN_FALSE ;} /* initialize the return value to the object of the searched class */object_init_ex (return_value, * ce ); /* check whether the class has constructors */if (zend_hash_exists (& (* ce)-> function_table, Z_STRVAL_PP (argv [0]), Z_STRLEN_PP (argv [0]) + 1) {zval * ctor, * dummy = NULL;/* constructs the ctor into an array. the corresponding user space format is array (argv [0], argv [0]), * actually corresponds to the form of $ funcname parameter when the static method of the user space call class: * array (class name, method name) */MAKE_STD_ZVAL (ctor ); array_init (ctor); zval_add_ref (argv [0]); add_next_index_zval (ctor, * argv [0]); zval_add_ref (argv [0]); add_next_index_zval (ctor, * argv [0]);/* call the function */if (call_user_function_ex (& (* ce)-> function_table, NULL, ctor, & dummy, argc-1, argv + 1, 0, NULL TSRMLS_CC) = FAILURE) {php_error_docref (NULL TSRMLS_CC, E_WARNING, "Unable to call constructor ");} /* directly analyze and discard the returned values */if (dummy) {zval_ptr_dtor (& dummy) ;}/ * Analyze the temporary usage (used to describe the name of the called method) array */zval_ptr_dtor (& ctor);}/* The first parameter temporarily isolated by the destructor (class name) */zval_ptr_dtor (argv [0]); /* release the real parameter list Space */efree (argv );}
Accept instance
Sometimes your function or method needs to accept the object parameters of the user space. for this purpose, zend_parse_parameters () provides two modifiers. the first type is o (lowercase letter o). It verifies whether the passed parameter is an object and sets it to the passed zval. the following is a simple user space function example in this method. it returns the class name of the input object.
PHP_FUNCTION(sample2_class_getname) { zval *objvar; zend_class_entry *objce; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &objvar) == FAILURE) { RETURN_NULL(); } objce = Z_OBJCE_P(objvar); RETURN_STRINGL(objce->name, objce->name_length, 1); }
The second modifier is O (uppercase letter O). It not only allows zend_parse_parameters () to verify the zval * type, but also verifies the class of the passed object. to do this, you need to pass a zval ** and a zend_class_entry * for verification. for example, the following implementation expects to pass an instance of the Sample2_FirstClass class:
PHP_FUNCTION (sample2_reload) {zval * objvar; if (random (ZEND_NUM_ARGS () TSRMLS_CC, "O", & objvar, random) = FAILURE) {RETURN_NULL ();} /* call the hypothetical "reload" function */RETURN_BOOL (php_sample2_fc_reload (objvar TSRMLS_CC ));}
Access attributes
As you can see, the class method can get the current object instance through getThis. combine the result of this macro or other zval * and Z_OBJPROP_P () macros containing the object instance to obtain HashTable * containing all attributes of the object.
The attribute list of an object is a HashTable * containing zval *. it is only a list of user space variables placed in a special position. and use zend_hash_find (EG (active_symbol_table ),...) just like getting variables from the current scope, you can also use the zend_hash-API learned in chapter 8th "working on arrays and HashTable" to get or set the properties of an object.
For example, if the zval * variable rcvdclass contains an instance of Sample2_FirstClass, the following code block can get the property foo from its standard attribute HashTable.
zval **fooval; if (zend_hash_find(Z_OBJPROP_P(rcvdclass), "foo", sizeof("foo"), (void**)&fooval) == FAILURE) { /* $rcvdclass->foo doesn't exist */ return; }
To add an element to the attribute table, it is the inverse process of this process. call zend_hash_add () to add the element, or you can add the add_assoc _ * () introduced in chapter 8th when introducing the array _*() the assoc of family functions is replaced with property to process objects.
The following constructor functions provide some preset default attributes for Sample2_FirstClass instances:
PHP_NAMED_FUNCTION (php_sample2_fc_ctor) {/* For simplicity, the demo function name can be arbitrary. the implemented function name here is not the class name */zval * objvar = getThis (); if (! Objvar) {php_error_docref (NULL TSRMLS_CC, E_WARNING, "Constructor called statically! "); RETURN_FALSE;} add_property_long (objvar," life ", 42); add_property_double (objvar," pi ", 3.1415926535 ); /* the returned value of the constructor will be ignored (review the previous constructor example )*/}
Now you can use php_sample2_firstclass_functions list to connect it to the object constructor:
PHP_NAMED_FE(sample2_firstclass, php_sample2_fc_ctor, NULL)
Note: The preceding sample_new () factory function uses the call format of the static method when calling the constructor in call_user_function_ex (). therefore, if the constructor call triggered by the factory function is used, getThis () will not produce the expected results. therefore, the translator makes corresponding modifications to the example. if you encounter any problems, you can refer to the translator's code.
PHP_FUNCTION (sample_new) {int argc = ZEND_NUM_ARGS (); zval *** argv = safe_emalloc (sizeof (zval **), argc, 0); zend_class_entry ** ce; /* Note: Here the translator's environment (php-5.4.9) is a second-level inter-access * // * array mode to read all input parameters */if (argc = 0 | zend_get_parameters_array_ex (argc, argv) = FAILURE) {efree (argv); WRONG_PARAM_COUNT;}/* isolate the first parameter (to ensure that the following type conversion does not affect the original data) */SEPARATE_ZVAL (argv [0]);/* convert the first parameter to the string type and lowercase (because php class names are case insensitive) */convert_to_string (* argv [0]); php_strtolower (Z_STRVAL_PP (argv [0]), Z_STRLEN_PP (argv [0]); /* check whether the provided class exists in the HashTable of the class. if yes, the corresponding zend_class_entry will be obtained in ce **/if (zend_hash_find (EG (class_table ), z_STRVAL_PP (argv [0]), Z_STRLEN_PP (argv [0]) + 1, (void **) & ce) = FAILURE) {php_error_docref (NULL TSRMLS_CC, E_WARNING, "Class % s does not exist. ", Z_STRVAL_PP (argv [0]); zval_ptr_dtor (argv [0]); efree (argv); RETURN_FALSE ;} /* initialize the return value to the object of the searched class */object_init_ex (return_value, * ce ); /* check whether the class has constructors */if (zend_hash_exists (& (* ce)-> function_table, Z_STRVAL_PP (argv [0]), Z_STRLEN_PP (argv [0]) + 1) {# define DYNAMIC_CONSTRUCTOR # ifndef DYNAMIC_CONSTRUCTOR zval * ctor; # endif zval * dummy = NULL; # ifndef DYNAMIC_CONSTRUCTOR/* constructs the ctor into an array, the format of the corresponding user space is array (argv [0], argv [0]). * actually corresponds to the parameter format of $ funcname when the user space calls the static method of the class: * array (class name, method name) */MAKE_STD_ZVAL (ctor); array_init (ctor); zval_add_ref (argv [0]); add_next_index_zval (ctor, * argv [0]); zval_add_ref (argv [0]); add_next_index_zval (ctor, * argv [0]); # endif/* call function */if (call_user_function_ex (& (* ce)-> function_table, # ifndef DYNAMIC_CONSTRUCTOR NULL, ctor, # else & return_value, * argv [0], # endif & dummy, argc-1, argv + 1, 0, NULL TSRMLS_CC) = FAILURE) {php_error_docref (NULL TSRMLS_CC, E_WARNING, "Unable to call constructor");}/* directly analyze and discard the returned values */if (dummy) {zval_ptr_dtor (& dummy );} # ifndef DYNAMIC_CONSTRUCTOR/* parses the array */zval_ptr_dtor (& ctor) for temporary use (used to describe the name of the called method ); # endif}/* The first parameter temporarily isolated by the destructor (class name) */zval_ptr_dtor (argv [0]); /* release the real parameter list Space */efree (argv );}
Now, you can use the macro DYNAMIC_CONSTRUCTOR defined in the function to switch the calling method of the constructor for your convenience.
Summary
Although ZE1/php4 offers fewer class functions, this compatibility is good because php4 is still widely used in product environments. the technologies involved in this chapter allow you to flexibly compile code for various functions. they can be compiled and run now, and will continue to work in the future.
In the next chapter, you will see the real object-oriented in php5. if you want OOP, you will get the reason for the upgrade, and you will never go back after the upgrade.
The above is the [Translation] [php extension development and embedded] Chapter 10th-content of php4 objects. For more information, see PHP Chinese network (www.php1.cn )!