📜 ⬆️ ⬇️

PHP extension. Writing a simple array with Traversable and ArrayAccess

In this article, I propose using the example of a simple array to consider exactly how the internal interfaces Traversable and ArrayAccess work.

Immediately I will give a list of resources, which will be many links later in the text.

And about the platform: I wrote code for ubuntu, so for other linux distributions (and OS X) you need a minimum of changes (change apt-get). If you want to write under Windows, then you will have to search for information on other Internet ( no one writes code on windows anyway ).


')

We collect PHP


To get started, let's build a debug version of PHP. You can, of course, write an extension with the regular version, but after a couple of debug-flags, PHP becomes much more talkative.

Open the console, go to the directory where we are going to pull PHP source files (for example ~ / dev / c /) and take the php code from the git repository.
git clone http://git.php.net/repository/php-src.git cd php-src 

Switch to a fresh branch.
 git checkout PHP-5.6 

Install the necessary programs for building PHP (most likely you already have them).
 sudo apt-get install build-essential autoconf automake libtool 

Now it remains to install bison. In ubuntu from version 14, bison comes version 3 and higher, which PHP does not digest. We need version 2.7.
 wget http://launchpadlibrarian.net/140087283/libbison-dev_2.7.1.dfsg-1_amd64.deb wget http://launchpadlibrarian.net/140087282/bison_2.7.1.dfsg-1_amd64.deb sudo dpkg -i libbison-dev_2.7.1.dfsg-1_amd64.deb sudo dpkg -i bison_2.7.1.dfsg-1_amd64.deb 


Since we will build a version without default extensions, we do not need libxml2. Otherwise, you will need to install libxml2-dev.
 sudo apt-get install libxml2-dev 

Configure indicates that we need a debug-version, without extensions. In the parameter --prefix we specify the directory in which PHP will be installed.
 ./buildconf ./configure --disable-all --enable-debug --prefix=$HOME/dev/bin/php make && make install 


Oke, PHP is ready. We will run the newest php with the -v flag and make sure that we have collected what we need and where we need it (and then you never know).
 ~/dev/bin/php/bin/php -v 


We collect expansion


The “skeleton” of the extension can be quickly generated using ext_skel , which is located in the PHP source directory. We’ll give up ext_skel, because besides the useful .gitignore it craps us with hundreds of unnecessary comments into files. And .gitignore can be taken here .

If you really want ext_skel, then you need to run it with the following parameters: the name of the extension is specified in - extraname, and the path to the skeleton folder in - skel.
 ~/dev/c/php-src/ext/ext_skel --extname=jco --skel=$HOME/dev/c/php-src/ext/skeleton/ 


One way or another, you should have a directory with the following files.
 jco/ .gitignore config.m4 config.w32 jco.c php_jco.h 


Open config.m4 and write:
 if test "$PHP_JCO" = "yes"; then AC_DEFINE(HAVE_JCO, 1, [Whether you have Jco]) PHP_NEW_EXTENSION(jco, jco.c, $ext_shared) fi 

Everything further in config.m4 we will touch only the line with PHP_NEW_EXTENSION, adding new files there.

Now let's write the main header file of our extension: php_jco.h. It must be called php_% extension name% .h
 #ifndef PHP_JCO_H #define PHP_JCO_H 1 extern zend_module_entry jco_module_entry; #define phpext_jco_ptr &jco_module_entry //   - ,     . #ifdef ZTS #include "TSRM.h" #endif #endif 

In this file, we declare a variable of type zend_module_entry with information about our extension. The name of the variable should be % extension name% _module_entry.

Open jco.c and write the following to it.
jco.c
 #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_jco.h" //    PHP_FUNCTION(hello_from_jco) { //   ,       . RETURN_STRING("JCO ENABLED! YEY!", 1); } //  PHP    ,      . const zend_function_entry jco_functions[] = { PHP_FE(hello_from_jco, NULL) PHP_FE_END }; //  ,  php       PHP_MINIT_FUNCTION(jco_init) { return SUCCESS; } zend_module_entry jco_module_entry = { STANDARD_MODULE_HEADER, "jco", //   jco_functions, PHP_MINIT(jco_init), NULL, // MSHUTDOWN NULL, // RINIT NULL, // RSHUTDOWN NULL, // MINFO "0.1", //  STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_JCO ZEND_GET_MODULE(jco) #endif 


The main thing here is the definition of a variable with information about our module, in which we specified the table of functions, the start function and other necessary data (or did not specify, replacing with NULL). And ZEND_GET_MODULE simply creates the get_module function for our library, which returns the variable jco_module_entry;

Great, now we are ready to build our extension. Let's run phpize, which will make us configs for the config collector for the extension assembler (we need to go deeper!)
 ~/dev/bin/php/bin/phpize 

And collect the extension. In the parameter - with-php-config specify the path to the file php-config of the debug-version of PHP compiled by us
 ./configure --with-php-config=$HOME/dev/bin/php/bin/php-config make && make install 

If everything is assembled without errors, then run php with the extension (if not, we are correct and still run).
 ~/dev/bin/php/bin/php -dextension=jco.so --r "hello_from_jco();" JCO ENABLED! YEY! 


Briefly about zval and functions


Before we move on to the classes, we’ll briefly review what PHP offers for working with functions and variables.

To declare a function, use the PHP_FUNCTION, PHP_NAMED_FUNCTION, or PHP_METHOD macros. They differ only in the name of the function obtained.
 void prefix_name(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used void ***tsrm_ls) 

Where


Function arguments are defined using the ZEND_ARG_INFO_ * macros.
 // , _,   ,    ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 1) //   ,   ZEND_ARG_INFO(0, var1) ZEND_ARG_INFO(0, var2) ZEND_END_ARG_INFO() /*    static const zend_arg_info arginfo_construct[] = { { NULL, 0, NULL, 2, 0, 0, 0, 0 }, { "var1", sizeof("var1")-1, NULL, 0, 0, 0, 0, 0 }, { "var2", sizeof("var2")-1, NULL, 0, 0, 0, 0, 0 }, } */ 

ZEND_BEGIN_ARG_INFO_EX , ZEND_ARG_INFO and ZEND_END_ARG_INFO will result in an array of zend_arg_info structures. Moreover, the first element of the array is cast to the type zend_internal_function_info . The number and types of fields they have the same, only the names differ.

The following functions, using macos PHP_FE, PHP_ME, PHP_ME_MAPPING, are listed in the table of module / class functions by elements of type zend_function_entry .
 typedef struct _zend_function_entry { const char *fname; //     PHP void (*handler)(INTERNAL_FUNCTION_PARAMETERS); //   const struct _zend_arg_info *arg_info; //    zend_uint num_args; //   ( ,    arg_info) zend_uint flags; //   } zend_function_entry 

When registering a module, the functions are entered into the global function table (function_table). When registering a class - in the table of class functions.

To get the arguments, use the function zend_parse_parameters , which is called with the following parameters.

You can read about zend_parse_parameters here .

To work with variables, PHP uses zval , which stores the value itself in zvalue_value , its type, reference count, and flag indicating that the interchange is used by reference.
 struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; }; 

To allocate memory for zval, use ALLOC_ZVAL (just allocates memory), MAKE_STD_ZVAL (ALLOC_ZVAL + initialization of values) and others.

This should be done because instead of zval ALLOC_ZVAL will allocate memory for _zval_gc_info , which additionally stores information for searching for circular references.

To remove zval, use the zval_ptr_dtor function. Unlike zval_dtor , zval_ptr_dtor first decreases the reference count and removes zval only if the counter becomes zero.

It is also worth considering that zvalue_value stores pointers for all values ​​that are more complex than numbers. And therefore, if you have two zval references to the same line in memory, then when you delete one of them, the second one will already refer to the incorrect memory location.

Read more about zval You can read in phpintenralsbook . And about circular references in the PHP manual .

Classes


Let's return to our expansion and add the first class. Create a file jco_darray.h and write the following there.
 #ifndef PHP_JCO_DARRAY_H #define PHP_JCO_DARRAY_H 1 extern zend_class_entry *jco_darray_ce; void jco_darray_init(TSRMLS_D); #endif 

Here we announced the jco_darray_ce variable of the zend_class_entry type for the class and the function for initialization.

Now create the jco_darray.c file.
jco_darray.c
 #include "php.h" #include "jco_darray.h" zend_class_entry *jco_darray_ce; PHP_METHOD(jco_darray, sayHello) { RETURN_STRING("Hello from darray!", 1); } const zend_function_entry jco_darray_functions[] = { //  ,  , arginfo,  PHP_ME(jco_darray, sayHello, NULL, ZEND_ACC_PUBLIC) PHP_FE_END }; void jco_darray_init(TSRMLS_D) { zend_class_entry tmp_ce; INIT_CLASS_ENTRY(tmp_ce, "JCO\\DArray", jco_darray_functions); jco_darray_ce = zend_register_internal_class(&tmp_ce TSRMLS_CC); return; } 


Here only the jco_darray_init function is interesting. First we create a temporary structure for our tmp_ce class and fill it with INIT_CLASS_ENTRY . The second macro parameter specifies the class name that is available from PHP, including the namespace .

We use the function zend_register_internal_class , which registers our class in the class table (class_table).

Now we add a call to the jco_darray_init function to the jco_init function (jco.h file).
 PHP_MINIT_FUNCTION(jco_init) { jco_darray_init(TSRMLS_C); return SUCCESS; } 

And add a new jco_darray.c file in config.m4 (the list of files is specified WITHOUT commas).
 PHP_NEW_EXTENSION(jco, jco.c jco_darray.c, $ext_shared) 

Since we changed config.m4, we need to run phpize again
 ~/dev/bin/php/bin/phpize --clean ~/dev/bin/php/bin/phpize ./configure --with-php-config=$HOME/dev/bin/php/bin/php-config make && make install 

Let's make a php script to test our extension (let's call it original: jco.php)
 <?php $darray = new \JCO\DArray(); echo $darray->sayHello() . PHP_EOL; ?> 

And run the script with our extension
 ~/dev/bin/php/bin/php -dextension=jco.so jco.php 


D for Dynamic


With a class that only knows how to say “Hello”, you will not get far. Especially if it was conceived as an array. Time to take and write this array.

Create the ds directory and add the darray.h file there, in which we will declare the structure and functions for our array.
ds / drray.h
 #ifndef PHP_JCO_DS_DARRAY_H #define PHP_JCO_DS_DARRAY_H 1 #include "php.h" typedef struct jco_ds_darray { size_t count; //  NULL  size_t length; //    size_t min_length; //    size_t capacity; //  -      void *elements; //   (zval) } jco_ds_darray; jco_ds_darray *jco_ds_darray_create(size_t size, size_t capacity); void jco_ds_darray_destroy(jco_ds_darray *array); #define jco_ds_darray_length(array) ((array)->length) #define jco_ds_darray_min_length(array) ((array)->min_length) #define jco_ds_darray_capacity(array) ((array)->capacity) #define jco_ds_darray_count(array) ((array)->count) #define jco_ds_darray_first(array) ((zval *)(array)->elements) #endif 


Now in the ds / darray.c file we define the functions declared above. For now, this is only creating and deleting a structure.
ds / darray.c
 #include "ds/darray.h" #include "php.h" jco_ds_darray *jco_ds_darray_create(size_t size, size_t capacity) { jco_ds_darray *array = emalloc(sizeof(jco_ds_darray)); if (!array) { return NULL; } array->count = 0; array->length = 0; array->min_length = size; array->capacity = capacity; array->elements = NULL; return array; } void jco_ds_darray_destroy(jco_ds_darray *array) { if (!array) { return; } efree(array); } 


We have a class, we have an array, and we need to somehow bind them. To do this, let's clarify how php works with objects.

To store objects in variables (which are zval), the structure is zend_object_value, which has the following fields.
 typedef struct _zend_object_value { zend_object_handle handle; const zend_object_handlers *handlers; } zend_object_value; 


So, all we need is to give our structure, which expands the zend_object. To do this, we write our own function create_object and a function to free the memory allocated for the structure. Add them after the jco_darray_ce declaration.
jco_darray.c
 zend_object_handlers jco_darray_handlers; typedef struct jco_darray { zend_object std; jco_ds_darray *array; } jco_darray; static void jco_darray_free_object_storage(jco_darray *intern TSRMLS_DC) { zend_object_std_dtor(&intern->std TSRMLS_CC); if (intern->array) { jco_ds_darray_destroy(intern->array); } efree(intern); } zend_object_value jco_darray_create_object(zend_class_entry *class_type TSRMLS_DC) { zend_object_value retval; jco_darray *intern = emalloc(sizeof(jco_darray)); memset(intern, 0, sizeof(jco_darray)); // :         zend_object_std_init(&intern->std, class_type TSRMLS_CC); //     object_properties_init(&intern->std, class_type); //      (object_store) retval.handle = zend_objects_store_put( intern, (zend_objects_store_dtor_t) zend_objects_destroy_object, //    (zend_objects_free_object_storage_t) jco_darray_free_object_storage, //      NULL //  ,    TSRMLS_CC ); //     -   retval.handlers = &jco_darray_handlers; return retval; } 


zend_objects_store_put takes three functions:


Add the following lines to the jco_darray_init function
  //     zend_object jco_darray_ce->create_object = jco_darray_create_object; //     memcpy(&jco_darray_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); 

And where is the array? And we will create an array in the constructor.
jco_darray.c
 PHP_METHOD(jco_darray, __construct) { jco_darray *intern; long size = 0; long capacity = 0; zend_error_handling error_handling; //   ,          zend_replace_error_handling(EH_THROW, NULL, &error_handling TSRMLS_CC); //      (l - long),  -    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &size, &capacity) == FAILURE) { zend_restore_error_handling(&error_handling TSRMLS_CC); return; } //     zend_restore_error_handling(&error_handling TSRMLS_CC); if (size <= 0) { zend_throw_exception(NULL, "Array size must be positive", 0 TSRMLS_CC); return; } if (capacity < 0) { zend_throw_exception(NULL, "Array capacity must be positive or 0", 0 TSRMLS_CC); return; } //    handle intern = zend_object_store_get_object(getThis() TSRMLS_CC); intern->array = jco_ds_darray_create((size_t)size, (size_t)capacity); if (!intern->array) { zend_throw_exception(NULL, "Failed to allocate array", 0 TSRMLS_CC); } return; } 


Add __construct to the table of functions.
 ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 1) //   ,   ZEND_ARG_INFO(0, size) ZEND_ARG_INFO(0, capacity) ZEND_END_ARG_INFO() const zend_function_entry jco_darray_functions[] = { PHP_ME(jco_darray, __construct, arginfo_construct, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, sayHello, arginfo_void, ZEND_ACC_PUBLIC) PHP_FE_END }; 


Time to build an extension and make sure everything compiles fine. Run phpize to catch config.m4 changes (I promise, this is the last time)
 ~/dev/bin/php/bin/phpize --clean ~/dev/bin/php/bin/phpize ./configure --with-php-config=$HOME/dev/bin/php/bin/php-config make && make install 

And run the test script
 ~/dev/bin/php/bin/php -dextension=jco.so jco.php 


ArrayAccess


In the ds / darray.h file, we add declarations of functions for working with an array: get, set, unset (and clone at the same time).
 jco_ds_darray *jco_ds_darray_create(size_t size, size_t capacity); jco_ds_darray *jco_ds_darray_clone(jco_ds_darray *array); void jco_ds_darray_destroy(jco_ds_darray *array); zval *jco_ds_darray_get(jco_ds_darray *array, size_t index); zval *jco_ds_darray_set(jco_ds_darray *array, size_t index, zval *value); void jco_ds_darray_unset(jco_ds_darray *array, size_t index); 

And write these functions
ds / darray.s
  #include "ds/darray.h" #include "php.h" #define ELEM_SIZE (sizeof(zval)) //     . //   index,     capacity , //     index-  static inline int _jco_ds_darray_expand(jco_ds_darray *array, size_t index) { if (array && array->capacity > 0) { size_t capacity = array->capacity; size_t max_elements = array->length; size_t expand_count; if (index) { expand_count = ((index + 1) / capacity) * capacity + capacity; } else { expand_count = (max_elements + capacity); } zval *elements; if (max_elements == 0 && !array->elements) { elements = (zval *)emalloc(ELEM_SIZE * expand_count); } else { elements = (zval *)erealloc((void *)array->elements, ELEM_SIZE * expand_count); } if (elements) { zval *ptr = (elements + max_elements); memset(ptr, 0, array->capacity * ELEM_SIZE); array->length = expand_count; array->elements = elements; return 1; } return 0; } return 0; } jco_ds_darray *jco_ds_darray_create(size_t size, size_t capacity) { jco_ds_darray *array = emalloc(sizeof(jco_ds_darray)); if (!array) { return NULL; } array->length = 0; array->min_length = size; array->capacity = size; array->count = 0; array->elements = NULL; if (size > 0 && !_jco_ds_darray_expand(array, 0)) { efree(array); return NULL; } array->length = size; array->capacity = capacity; return array; } void jco_ds_darray_destroy(jco_ds_darray *array) { if (!array) { return; } if (array->length > 0) { zval *elem = (zval *)array->elements; while (array->length--) { if (elem != NULL && Z_REFCOUNT_P(elem) > 0) { zval_dtor(elem); } elem++; } } if (array->elements) { efree(array->elements); } efree(array); } jco_ds_darray *jco_ds_darray_clone(jco_ds_darray *array) { if (!array) { return NULL; } jco_ds_darray *new_array = emalloc(sizeof(jco_ds_darray)); if (!new_array) { return NULL; } new_array->count = array->count; new_array->length = array->length; new_array->min_length = array->min_length; new_array->capacity = array->capacity; new_array->elements = (zval *)emalloc(ELEM_SIZE * array->length); if (!new_array->elements) { efree(new_array); return NULL; } memcpy(new_array->elements, array->elements, ELEM_SIZE * array->length); //memcpy    zval`,         //        zval_copy_ctor size_t index; for (index = 0; index < array->length; index++) { zval *elem = (zval *)new_array->elements + index; if (elem != NULL && Z_REFCOUNT_P(elem) > 0) { zval_copy_ctor(elem); } } return new_array; } zval *jco_ds_darray_get(jco_ds_darray *array, size_t index) { if (!array || array->length < (index + 1)) { return NULL; } zval *elem = (zval *)(array->elements) + index; if (!elem || Z_TYPE_P(elem) == IS_NULL) { return NULL; } //   ,  is_ref__gc = 0 Z_UNSET_ISREF_P(elem); return elem; } void jco_ds_darray_unset(jco_ds_darray *array, size_t index) { if (!array || array->length < (index + 1)) { return; } zval *elem = (zval *)array->elements + index; if (elem != NULL && Z_REFCOUNT_P(elem) > 0) { if (Z_TYPE_P(elem) != IS_NULL) { array->count--; } zval_dtor(elem); *elem = (zval) {0}; } } zval *jco_ds_darray_set(jco_ds_darray *array, size_t index, zval *value) { if (!array) { return; } if ((index + 1) > array->length) { if (array->capacity == 0) { return NULL; } if (!_jco_ds_darray_expand(array, index)) { return NULL; } } zval *elem = (zval *)array->elements + index; int prev_is_not_null = 0; if (Z_REFCOUNT_P(elem) > 0 && Z_TYPE_P(elem)) { zval_dtor(elem); prev_is_not_null = 1; } elem->value = value->value; elem->type = value->type; elem->refcount__gc = 1; elem->is_ref__gc = 0; zval_copy_ctor(elem); if (prev_is_not_null && Z_TYPE_P(elem) == IS_NULL) { array->count--; } else if (!prev_is_not_null && Z_TYPE_P(elem) != IS_NULL) { array->count++; } return elem; } 


As you can see, in jco_ds_darray_set ALLOC_ZVAL we did not use, but used previously allocated memory. In our case, it is important for us that the array of elements is continuous in memory. In addition, we will not give the array elements directly into the user code, so the GC will be superfluous. Accordingly, for deletion, we use zval_dtor instead of zval_ptr_dtor.

Now, using new functions, we implement the ArrayAccess interface.
jco_darray.c
  PHP_METHOD(jco_darray, count) { jco_darray *intern; long count; intern = zend_object_store_get_object(getThis() TSRMLS_CC); count = (long)jco_ds_darray_count(intern->array); ZVAL_LONG(return_value, count); } PHP_METHOD(jco_darray, length) { jco_darray *intern; long length; intern = zend_object_store_get_object(getThis() TSRMLS_CC); length = (long) jco_ds_darray_length(intern->array); ZVAL_LONG(return_value, length); } PHP_METHOD(jco_darray, offsetSet) { jco_darray *intern; zval *val; long index; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz", &index, &val) == FAILURE) { zend_throw_exception(NULL, "Failed to parse arguments", 0 TSRMLS_CC); return; } intern = zend_object_store_get_object(getThis() TSRMLS_CC); jco_ds_darray_set(intern->array, (size_t)index, val); } PHP_METHOD(jco_darray, offsetUnset) { jco_darray *intern; long index; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) == FAILURE) { zend_throw_exception(NULL, "Invalid index passed", 0 TSRMLS_CC); return; } intern = zend_object_store_get_object(getThis() TSRMLS_CC); jco_ds_darray_unset(intern->array, (size_t)index); } PHP_METHOD(jco_darray, offsetGet) { jco_darray *intern; long index; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) == FAILURE) { zend_throw_exception(NULL, "Invalid index passed", 0 TSRMLS_CC); return; } intern = zend_object_store_get_object(getThis() TSRMLS_CC); zval *val = jco_ds_darray_get(intern->array, (size_t)index); if (val) { //, ,  zval_copy_ctor,  zval_ptr_dtor ZVAL_ZVAL(return_value, val, 1, 0); } else { ZVAL_NULL(return_value); } } PHP_METHOD(jco_darray, offsetExists) { jco_darray *intern; long index; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) == FAILURE) { zend_throw_exception(NULL, "Invalid index passed", 0 TSRMLS_CC); return; } intern = zend_object_store_get_object(getThis() TSRMLS_CC); zval *val = jco_ds_darray_get(intern->array, (size_t)index); if (val) { ZVAL_TRUE(return_value); } else { ZVAL_FALSE(return_value); } } 



Add functions to the class function table.
jco_darray.c
 ZEND_BEGIN_ARG_INFO_EX(arginfo_jco_darray_offset, 0, 0, 1) ZEND_ARG_INFO(0, offset) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_jco_darray_offset_value, 0, 0, 2) ZEND_ARG_INFO(0, offset) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() const zend_function_entry jco_darray_functions[] = { PHP_ME(jco_darray, __construct, arginfo_construct, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, offsetSet, arginfo_jco_darray_offset_value, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, offsetGet, arginfo_jco_darray_offset, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, offsetUnset, arginfo_jco_darray_offset, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, offsetExists, arginfo_jco_darray_offset, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, count, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(jco_darray, length, arginfo_void, ZEND_ACC_PUBLIC) PHP_FE_END }; 



, php ArrayAccess
  zend_class_implements(jco_darray_ce TSRMLS_CC, 1, zend_ce_arrayaccess); 

— class_entry .

zend_ce_arrayaccess zend_interfaces.h ( zend_ce_traversable, zend_ce_aggregate, zend_ce_iterator zend_ce_serializable), jco_darray.c
 #include "php.h" #include "zend_interfaces.h" #include "jco_darray.h" #include "ds/darray.h" 


 <?php ini_set("memory_limit", "512M"); $data = range(1, 500000); $t1 = microtime(true); $m1 = memory_get_usage(); $jar = new \JCO\DArray(500000, 0); foreach($data as $index => &$val) { $jar[$index] = $val * 3; } echo "JCO\Darray" . PHP_EOL; echo "TIME: " . (microtime(true) - $t1) . PHP_EOL; echo "MEMORY: " . ((memory_get_usage() - $m1)/1048576) . PHP_EOL; gc_collect_cycles(); $t1 = microtime(true); $m1 = memory_get_usage(); $ar = []; foreach($data as $index => &$val) { $ar[$index] = $val * 3; } echo "AR" . PHP_EOL; echo "TIME: " . (microtime(true) - $t1) . PHP_EOL; echo "MEMORY: " . ((memory_get_usage() - $m1)/1048576) . PHP_EOL; gc_collect_cycles(); ?> 


C php c
  make && make install 

 ~/dev/bin/php/bin/php -dextension=jco.so jco.php JCO\Darray TIME: 0.43633484840393 MEMORY: 11.44548034668 Array TIME: 0.3345410823822 MEMORY: 137.51664733887 


… php!

Object Handlers


, . object_handlers , .
 ZEND_API zend_object_handlers std_object_handlers = { zend_objects_store_add_ref, /* add_ref */ zend_objects_store_del_ref, /* del_ref */ zend_objects_clone_obj, /* clone_obj */ zend_std_read_property, /* read_property */ zend_std_write_property, /* write_property */ zend_std_read_dimension, /* read_dimension */ zend_std_write_dimension, /* write_dimension */ zend_std_get_property_ptr_ptr, /* get_property_ptr_ptr */ NULL, /* get */ NULL, /* set */ zend_std_has_property, /* has_property */ zend_std_unset_property, /* unset_property */ zend_std_has_dimension, /* has_dimension */ zend_std_unset_dimension, /* unset_dimension */ zend_std_get_properties, /* get_properties */ zend_std_get_method, /* get_method */ NULL, /* call_method */ zend_std_get_constructor, /* get_constructor */ zend_std_object_get_class, /* get_class_entry */ zend_std_object_get_class_name, /* get_class_name */ zend_std_compare_objects, /* compare_objects */ zend_std_cast_object_tostring, /* cast_object */ NULL, /* count_elements */ zend_std_get_debug_info, /* get_debug_info */ zend_std_get_closure, /* get_closure */ zend_std_get_gc, /* get_gc */ NULL, /* do_operation */ NULL, /* compare */ }; 


, , : read_dimension, write_dimension, has_dimension unset_dimension.

zend_std_read_dimension , , ArrayAccess offsetGet. php , , (!) .

: ( count clone).
jco_darray.c
 // ,   zval  long static inline long zval_to_long(zval *zv) { if (Z_TYPE_P(zv) == IS_LONG) { return Z_LVAL_P(zv); } else { zval tmp = *zv; zval_copy_ctor(&tmp); convert_to_long(&tmp); return Z_LVAL(tmp); } } static zend_object_value jco_darray_clone(zval *object TSRMLS_DC) { jco_darray *old_object = zend_object_store_get_object(object TSRMLS_CC); zend_object_value new_object_val = jco_darray_create_object(Z_OBJCE_P(object) TSRMLS_CC); jco_darray *new_object = zend_object_store_get_object_by_handle(new_object_val.handle TSRMLS_CC); //   zend_objects_clone_members( &new_object->std, new_object_val, &old_object->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC ); new_object->array = jco_ds_darray_clone(old_object->array); if (!new_object->array) { zend_throw_exception(NULL, "Failed to clone jco_darray", 0 TSRMLS_CC); } return new_object_val; } static zval *jco_darray_read_dimension(zval *object, zval *zv_offset, int type TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->read_dimension(object, zv_offset, type TSRMLS_CC); } if (!zv_offset) { zend_throw_exception(NULL, "Cannot append to a jco_darray", 0 TSRMLS_CC); return NULL; } long offset = zval_to_long(zv_offset); if (offset < 0 || offset > jco_ds_darray_length(intern->array)) { zend_throw_exception(NULL, "Offset out of range", 0 TSRMLS_CC); return NULL; } zval *return_value; zval *value = jco_ds_darray_get(intern->array, offset); if (value) { if (type != BP_VAR_R && type != BP_WAR_RW) { return_value = value; Z_SET_ISREF_P(return_value); } else { MAKE_STD_ZVAL(return_value); ZVAL_ZVAL(return_value, value, 1, 0); Z_DELREF_P(return_value); } } else { MAKE_STD_ZVAL(return_value); ZVAL_NULL(return_value); Z_DELREF_P(return_value); } return return_value; } static void jco_darray_write_dimension(zval *object, zval *zv_offset, zval *value TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->write_dimension(object, zv_offset, value TSRMLS_CC); } if (!zv_offset) { zend_throw_exception(NULL, "Cannot append to a jco_darray", 0 TSRMLS_CC); } long offset = zval_to_long(zv_offset); if (offset < 0) { zend_throw_exception(NULL, "Offset out of range", 0 TSRMLS_CC); } zval *saved_val = jco_ds_darray_set(intern->array, (size_t)offset, value); if (saved_val == NULL) { zend_throw_exception(NULL, "Error occured during dimension write", 0 TSRMLS_CC); } } static int jco_darray_has_dimension(zval *object, zval *zv_offset, int check_empty TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->has_dimension(object, zv_offset, check_empty TSRMLS_CC); } long offset = zval_to_long(zv_offset); if (offset < 0 || offset > jco_ds_darray_length(intern->array)) { return 0; } zval *value = jco_ds_darray_get(intern->array, offset); if (value == NULL) { return 0; } if (check_empty) { return zend_is_true(value); } else { return Z_TYPE_P(value) != IS_NULL; } } static void jco_darray_unset_dimension(zval *object, zval *zv_offset TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->unset_dimension(object, zv_offset TSRMLS_CC); } long offset = zval_to_long(zv_offset); if (offset < 0 || offset > jco_ds_darray_length(intern->array)) { zend_throw_exception(NULL, "Offset out of range", 0 TSRMLS_CC); } jco_ds_darray_unset(intern->array, offset); } int jco_darray_count_elements(zval *object, long *count TSRMLS_DC) { jco_darray *intern = zend_object_store_get_object(object TSRMLS_CC); if (intern->std.ce->parent) { return zend_get_std_object_handlers()->count_elements(object, count TSRMLS_CC); } if (intern && intern->array) { *count = (long)jco_ds_darray_count(intern->array); return SUCCESS; } else { *count = 0; return FAILURE; } } 



jco_darray_read_dimension, type. , , BP_VAR_R, BP_VAR_W, BP_VAR_RW, BP_VAR_IS BP_VAR_UNSET.
 $var[0][1]; //   read_dimension BP_VAR_R $var[0][1] = 1; // [0] - read_dimension BP_VAR_W,  [1] - write_dimension isset($var[0][1]); // [0] - read_dimension BP_VAR_IS, [1] - has_dimension 

type , . BP_VAR_W , , zval->is_ref__gc = 1 ( ).

(intern->std.ce->parent). - ArrayAccess.

php , jco_darray_init
  jco_darray_handlers.has_dimension = jco_darray_has_dimension; jco_darray_handlers.read_dimension = jco_darray_read_dimension; jco_darray_handlers.write_dimension = jco_darray_write_dimension; jco_darray_handlers.unset_dimension = jco_darray_unset_dimension; jco_darray_handlers.count_elements = jco_darray_count_elements; jco_darray_handlers.clone_obj = jco_darray_clone; 


C php c
  make && make install 

 ~/dev/bin/php/bin/php -dextension=jco.so jco.php JCO\Darray TIME: 0.18597507476807 MEMORY: 11.44548034668 Array TIME: 0.33455300331116 MEMORY: 137.51664733887 


, . !

Traversable



, . object_handlers , zend_class_entry get_iterator iterator_funcs .

get_iterator zend_object_iterator , ( foreach).
 struct _zend_object_iterator { void *data; //   .   zend_object_iterator_funcs *funcs; //        ulong index; //  .      }; 


iterator_funcs, , : , Iterator IteratorAggregate. zf_* — (?) php . funcs _zend_object_iterator. , - iterator_funcs.

jco_darray.c jco_darray , .
 typedef struct _jco_darray_iterator_data { zval *object_zval; //  php  (,        ) jco_darray *object; //   zend_object size_t offset; //   zval *current; //   } jco_darray_iterator_data; 


get_iterator. jco_darray.c count_elements jco_darray_get_iterator.
 //by_ref - ,      . zend_object_iterator *jco_darray_get_iterator(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC) { zend_object_iterator *iter; jco_darray_iterator_data *iter_data; if (by_ref) { zend_throw_exception(NULL, "UPS, no by reference iteration!", 0 TSRMLS_CC); return NULL; } iter = emalloc(sizeof(zend_object_iterator)); iter->funcs = &jco_darray_iterator_funcs; iter_data = emalloc(sizeof(jco_darray_iterator_data)); iter_data->object_zval = object; Z_ADDREF_P(object); iter_data->object = zend_object_store_get_object(object TSRMLS_CC); iter_data->offset = 0; iter_data->current = NULL; iter->data = iter_data; return iter; } 


. , get_iterator.
jco_darray.c
 static void jco_darray_iterator_dtor(zend_object_iterator *intern TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *)intern->data; if (data->current != NULL) { zval_ptr_dtor(&data->current); } zval_ptr_dtor((zval **)&data->object_zval); efree(data); efree(intern); } static int jco_darray_iterator_valid(zend_object_iterator *intern TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *)intern->data; return jco_ds_darray_length(data->object->array) > data->offset ? SUCCESS : FAILURE; } // static void jco_darray_iterator_get_current_data(zend_object_iterator *intern, zval ***data TSRMLS_DC) { jco_darray_iterator_data *iter_data = (jco_darray_iterator_data *)intern->data; if (iter_data->current != NULL) { zval_ptr_dtor(&iter_data->current); iter_data->current = NULL; } if (iter_data->offset < jco_ds_darray_length(iter_data->object->array)) { zval *value = jco_ds_darray_get(iter_data->object->array, iter_data->offset); if (value != NULL) { MAKE_STD_ZVAL(iter_data->current); ZVAL_ZVAL(iter_data->current, value, 1, 0); *data = &iter_data->current; } else { *data = NULL; } } else { *data = NULL; } } #if ZEND_MODULE_API_NO >= 20121212 //  php 5.5+ static void jco_darray_iterator_get_current_key(zend_object_iterator *intern, zval *key TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *) intern->data; ZVAL_LONG(key, data->offset); } #else //             //   HASH_KEY_IS_STRING, HASH_KEY_IS_LONG  HASH_KEY_NON_EXISTANT static int jco_darray_iterator_get_current_key(zend_object_iterator *intern, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *) intern->data; *int_key = (ulong) data->offset; return HASH_KEY_IS_LONG; } #endif static void jco_darray_iterator_move_forward(zend_object_iterator *intern TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *) intern->data; data->offset++; } static void jco_darray_iterator_rewind(zend_object_iterator *intern TSRMLS_DC) { jco_darray_iterator_data *data = (jco_darray_iterator_data *) intern->data; data->offset = 0; data->current = NULL; } static zend_object_iterator_funcs jco_darray_iterator_funcs = { jco_darray_iterator_dtor, jco_darray_iterator_valid, jco_darray_iterator_get_current_data, jco_darray_iterator_get_current_key, jco_darray_iterator_move_forward, jco_darray_iterator_rewind, NULL }; 



jco_darray_init get_iterator.
  jco_darray_ce->get_iterator = jco_darray_get_iterator; jco_darray_ce->iterator_funcs.funcs = &jco_darray_iterator_funcs; 


foreach
 foreach($jar as $val) { if(($val % 100000) == 0) { echo $val . PHP_EOL; } } 


C php c
  make && make install 

 ~/dev/bin/php/bin/php -dextension=jco.so jco.php 


Conclusion


, , . Traversable ArrayAccess , , PHP. , php internals book.

, phpng ( ), , , .

github- .

, . .

Source: https://habr.com/ru/post/260983/


All Articles