PHP Classes

How to Create a PHP C Extension to Manipulate Arrays Part 2: Adding ArrayAccess and Traversable interfaces

Recommend this page to a friend!
  Blog PHP Classes blog   RSS 1.0 feed RSS 2.0 feed   Blog How to Create a PHP C...   Post a comment Post a comment   See comments See comments (1)   Trackbacks (0)  

Author:

Viewers: 246

Last month viewers: 5

Categories: PHP Tutorials, PHP Performance

In the first part of this article we learned how to create an extension for PHP written in C to create a class that works like arrays. However, to make the class objects really behave as arrays you need to implement certain interfaces in the class.

Read this article to learn how to make a PHP class defined by a C extension implement ArrayAccess and Traversable interfaces, as well understand how to solve problems that you may encounter that can make your extension slower than you expect.




Loaded Article

Contents

Introduction

Adding the ArrayAccess interface

Customizing your Class Object Handlers

Adding the Traversable interface

Conclusion


Introduction

In the first part of this article we have learned how we can create PHP extensions written in C and eventually perform tasks for specific purposes much faster than we could using pure PHP code.

The article explained how to define a class that can store data in arrays.

This part continues the previous article by explaining how you can implement certain interfaces. In this case, since we want our class objects to behave like arrays, we need to implement interfaces like ArrayAccess and traversable..

Adding the ArrayAccess interface

Lets first add the necessary functions to implement the ArrayAccess interface.

In the file ds/darray.h add the function declarations to work with an array: get, set, unset and in the same place to clone.

slobel_ds_darray *slobel_ds_darray_create( size_t size, size_t capacity);
slobel_ds_darray *slobel_ds_darray_clone( slobel_ds_darray *array);
void slobel_ds_darray_destroy( slobel_ds_darray *array);
zval *slobel_ds_darray_get( slobel_ds_darray *array, size_t index);
zval *slobel_ds_darray_set( slobel_ds_darray *array, size_t index, zval *value);
void slobel_ds_darray_unset( slobel_ds_darray *array, size_t index);

These functions are implemented in ds/darray.с:

#include "ds/darray.h"
#include "php.h"

#define ELEM_SIZE (sizeof(zval))

// Increase the memory for the array elements. 
// If an index number is specified, you allocate memory with capacity to store the elements upto element with the number index
static inline int _slobel_ds_darray_expand( slobel_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;
}


slobel_ds_darray *slobel_ds_darray_create( size_t size, size_t capacity) {
    slobel_ds_darray *array = emalloc( sizeof(slobel_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 && !_slobel_ds_darray_expand( array, 0)) {
        efree(array);

        return NULL;
    }

    array->length = size;
    array->capacity = capacity;

    return array;
}


void slobel_ds_darray_destroy( slobel_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);
}

slobel_ds_darray *slobel_ds_darray_clone(slobel_ds_darray *array) {
    if (!array) {
        return NULL;
    }

    slobel_ds_darray *new_array = emalloc( sizeof( slobel_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 copied only one zval values for all elements but they point to the same value.
    //To fix this you need to go through the elements and call function zval_copy_ctor

    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 *slobel_ds_darray_get( slobel_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;
    }

    // In any case, make sure is_ref__gc = 0
    Z_UNSET_ISREF_P(elem);
    return elem;
}


void slobel_ds_darray_unset( slobel_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 *slobel_ds_darray_set( slobel_ds_darray *array, size_t index, zval *value) {
    if (!array) {
        return;
    }

    if ((index + 1) > array->length) {
        if (array->capacity == 0) {
            return NULL;
        }

        if (!_slobel_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 may notice in slobel_ds_darray_set we did not use ALLOC_ZVAL, but used a previously allocated memory block. In our case, it is important that the array elements are contiguous in memory. In addition, elements of the array are not defined directly in the user's code, so garbage collection is not necessary. To remove elements we use instead zval_dtor zval_ptr_dtor.

Now, lets start using the new function features implemented by the ArrayAccess interface.

slobel_darray.c:

PHP_METHOD(slobel_darray, count)
{
    slobel_darray *intern;
    long count;

    intern = zend_object_store_get_object( getThis() TSRMLS_CC);
    count = (long)slobel_ds_darray_count( intern->array);

    ZVAL_LONG(return_value, count);
}

PHP_METHOD(slobel_darray, length)
{
    slobel_darray *intern;
    long length;

    intern = zend_object_store_get_object( getThis() TSRMLS_CC);
    length = (long) slobel_ds_darray_length( intern->array);

    ZVAL_LONG(return_value, length);
}

PHP_METHOD(slobel_darray, offsetSet)
{ slobel_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); slobel_ds_darray_set( intern->array, (size_t)index, val); } PHP_METHOD(slobel_darray, offsetUnset) { slobel_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); slobel_ds_darray_unset( intern->array, (size_t)index); } PHP_METHOD(slobel_darray, offsetGet) { slobel_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 = slobel_ds_darray_get( intern->array, (size_t)index); if (val) { //what to do, the source, cause zval_copy_ctor, cause zval_ptr_dtor. ZVAL_ZVAL( return_value, val, 1, 0); } else { ZVAL_NULL( return_value); } } PHP_METHOD(slobel_darray, offsetExists) { slobel_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 = slobel_ds_darray_get( intern->array, (size_t)index); if (val) { ZVAL_TRUE(return_value); } else { ZVAL_FALSE(return_value); } }

Lets add the feature functions to the table functions in the class.

slobel_darray.c

ZEND_BEGIN_ARG_INFO_EX( arginfo_slobel_darray_offset, 0, 0, 1)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX( arginfo_slobel_darray_offset_value, 0, 0, 2)
ZEND_ARG_INFO(0, offset)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

const zend_function_entry slobel_darray_functions[] = {
    PHP_ME( slobel_darray, __construct, arginfo_construct, ZEND_ACC_PUBLIC)
    PHP_ME( slobel_darray, offsetSet, arginfo_slobel_darray_offset_value, ZEND_ACC_PUBLIC)
    PHP_ME( slobel_darray, offsetGet, arginfo_slobel_darray_offset, ZEND_ACC_PUBLIC)
    PHP_ME( slobel_darray, offsetUnset, arginfo_slobel_darray_offset, ZEND_ACC_PUBLIC)
    PHP_ME( slobel_darray, offsetExists, arginfo_slobel_darray_offset, ZEND_ACC_PUBLIC)
    PHP_ME( slobel_darray, count, arginfo_void, ZEND_ACC_PUBLIC)
    PHP_ME( slobel_darray, length, arginfo_void, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

Now, we declare that our PHP class implements the ArrayAccess interface.

zend_class_implements(slobel_darray_ce TSRMLS_CC, 1, zend_ce_arrayaccess);

The last parameter in the function call is the number of interfaces and class_entry interfaces separated with commas.

zend_ce_arrayaccess declared in the file zend_interfaces.h (with zend_ce_traversable, zend_ce_aggregate, zend_ce_iterator and zend_ce_serializable), which we need to include in the file slobel_darray.c

#include "php.h"
#include "zend_interfaces.h"
#include "slobel_darray.h"
#include "ds/darray.h"

We write test code and at the same time is comparable with our class with the usual array:

<?php
ini_set("memory_limit", "512M");

$data = range(1, 500000);

$t1 = microtime(true);
$m1 = memory_get_usage();

$jar = new \SLOBEL\DArray(500000, 0);
foreach($data as $index => &$val) {
    $jar[$index] = $val * 3;
}

echo "SLOBEL\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();

?>

Lets compile and run our PHP extension:

make && make install

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

SLOBEL\Darray
TIME: 0.43633484840393
MEMORY: 11.44548034668
Array
TIME: 0.3345410823822
MEMORY: 137.51664733887

Hey, wait a minute, our code has turned out to be slower than the standard array of PHP!

Customizing your Class Object Handlers

Let's see why our array turned to be so slow. To do this, remember about object_handlers, which wer mentioned before.

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 */
};

To work with an object as an array, use the following function features: read_dimension, write_dimension, has_dimension and unset_dimension.

If we look at the code of zend_std_read_dimension, we see that there is a check to the interface ArrayAccess and a call to the class function offsetget. This is a call to a PHP function, I mean it as in a very, very slow operation!

The solution for this should be obvious: write the function for the read_dimension feature, as well as count and clone.

slobel_darray.c:

// Auxiliary function that takes a 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 slobel_darray_clone( zval *object TSRMLS_DC) {
    slobel_darray *old_object = zend_object_store_get_object( object TSRMLS_CC);

    zend_object_value new_object_val = slobel_darray_create_object( Z_OBJCE_P( object ) TSRMLS_CC);
    slobel_darray *new_object = zend_object_store_get_object_by_handle( new_object_val.handle TSRMLS_CC);

    // Copy the properties of an object.
    zend_objects_clone_members(
        &new_object->std, new_object_val,
        &old_object->std, Z_OBJ_HANDLE_P(object) TSRMLS_CC
    );

    new_object->array = slobel_ds_darray_clone( old_object->array );

    if (!new_object->array) {
        zend_throw_exception(NULL, "Failed to clone slobel_darray", 0 TSRMLS_CC);
    }

    return new_object_val;
}

 
static zval *slobel_darray_read_dimension( zval *object, zval *zv_offset, int type TSRMLS_DC) {
    slobel_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 slobel_darray", 0 TSRMLS_CC);
        return NULL;
    }

    long offset = zval_to_long(zv_offset);
    if (offset < 0 || offset > slobel_ds_darray_length( intern->array)) {
        zend_throw_exception(NULL, "Offset out of range", 0 TSRMLS_CC);
        return NULL;
    }

    zval *return_value;
    zval *value = slobel_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 slobel_darray_write_dimension( zval *object, zval *zv_offset, zval *value TSRMLS_DC) {
    slobel_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 slobel_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 = slobel_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 slobel_darray_has_dimension( zval *object, zval *zv_offset, int check_empty TSRMLS_DC) {
    slobel_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 > slobel_ds_darray_length( intern->array)) {
        return 0;
    }

    zval *value = slobel_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 slobel_darray_unset_dimension( zval *object, zval *zv_offset TSRMLS_DC) {
    slobel_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 > slobel_ds_darray_length( intern->array)) {
        zend_throw_exception(NULL, "Offset out of range", 0 TSRMLS_CC);
    }

    slobel_ds_darray_unset(intern->array, offset);
}

int slobel_darray_count_elements( zval *object, long *count TSRMLS_DC) {
    slobel_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)slobel_ds_darray_count( intern->array);
        return SUCCESS;
    } else {
        *count = 0;
        return FAILURE;
    }
}

It is interesting to note that the function slobel_darray_read_dimension, which is the third parameter takes an integer type. This is a flag indicating the context in which the function was called, and can have the values BP_VAR_R, BP_VAR_W, BP_VAR_RW, BP_VAR_IS or BP_VAR_UNSET.

$var[0][1]; // Both cases 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

If we ignore the type, and will always return a copy of the value, in the second case above nothing happens and the value of the array within the array does not change. To fix this, if we give the value BP_VAR_W directly from the array, so the garbage collector does not try to remove it, we put zval-> is_ref__gc = 1 (this is a hack).

In each function, we check the availability (intern-> std.ce-> parent). This is just in case someone extends our class and overwrites the methods ArrayAccess.

To use our PHP function instead of the standard, add the slobel_darray_init with the following lines.

slobel_darray_handlers.has_dimension   = slobel_darray_has_dimension;
slobel_darray_handlers.read_dimension  = slobel_darray_read_dimension;
slobel_darray_handlers.write_dimension = slobel_darray_write_dimension;
slobel_darray_handlers.unset_dimension = slobel_darray_unset_dimension;
slobel_darray_handlers.count_elements  = slobel_darray_count_elements;
slobel_darray_handlers.clone_obj = slobel_darray_clone;

Now we compile and run PHP with our extension.

make && make install

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

SLOBEL\Darray
TIME: 0.18597507476807
MEMORY: 11.44548034668
Array
TIME: 0.33455300331116
MEMORY: 137.51664733887

Now we can see memory consumption is lower and the performance of implementation almost doubled when compared with the previous results. Success!

Adding the Traversable interface

In order for our class to become quite an array, you need to make it iterable. You need to add iteration functions object_handlers structure but in zend_class_entry there is only the get_iterator function and the structure iterator_funcs.

get_iterator returns zend_object_iterator, which is used to iterate over the array elements using for instance the PHP foreach function.

struct _zend_object_iterator {
    void *data; // a pointer to the additional class data
    zend_object_iterator_funcs *funcs; // a pointer to a function iteration and removal of the iterator
    ulong index; // field opcodes. We will not touch it
};

iterator_funcs, as I understand it, you need to use custom code: a class that implements the interface Iterator or IteratorAggregate. Fields zf_ * - (cache?) are custom PHP functions. A similar field of funcs is a zend_object_iterator. It would be good if in the comments of PHP source code someone gave a full explanation of how iterator_funcs should be used .

In the file slobel_darray.c after defining the structure slobel_darray add a structure to store data relevant to the iteration.

typedef struct _slobel_darray_iterator_data {
    zval *object_zval; // a pointer to a php object (necessary to the process of iterating it suddenly destroyed)
    slobel_darray *object; // pointer to zend_object
    size_t offset; // current position
    zval *current; // current value
} slobel_darray_iterator_data;

Now write a function get_iterator. In slobel_darray.c after the function count_elements add function slobel_darray_get_iterator.

//by_ref - a flag indicating that the value of the requested link.
zend_object_iterator *slobel_darray_get_iterator( zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC) {
    zend_object_iterator *iter;
    slobel_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 = &slobel_darray_iterator_funcs;

    iter_data = emalloc( sizeof( slobel_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;
}

And then the function code for iteration. In order to avoid declaring them separately, add them to the function get_iterator.

slobel_darray.c

static void slobel_darray_iterator_dtor( zend_object_iterator *intern TSRMLS_DC) {
    slobel_darray_iterator_data *data = (slobel_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 slobel_darray_iterator_valid( zend_object_iterator *intern TSRMLS_DC) {
    slobel_darray_iterator_data *data = (slobel_darray_iterator_data *)intern->data;

    return slobel_ds_darray_length( data->object->array) > data->offset ? SUCCESS : FAILURE;
}


static void slobel_darray_iterator_get_current_data( zend_object_iterator *intern, zval ***data TSRMLS_DC) {
    slobel_darray_iterator_data *iter_data = (slobel_darray_iterator_data *)intern->data;

    if (iter_data->current != NULL) {
        zval_ptr_dtor(&iter_data->current);
        iter_data->current = NULL;
    }

    if (iter_data->offset < slobel_ds_darray_length(iter_data->object->array)) {
        zval *value = slobel_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+ version
static void slobel_darray_iterator_get_current_key( zend_object_iterator *intern, zval *key TSRMLS_DC) {
    slobel_darray_iterator_data *data = (slobel_darray_iterator_data *) intern->data;
    ZVAL_LONG(key, data->offset);
}
#else
// In earlier versions of string and numeric keys should be given to the individual variables
// and return HASH_KEY_IS_STRING, HASH_KEY_IS_LONG or HASH_KEY_NON_EXISTANT
static int slobel_darray_iterator_get_current_key( zend_object_iterator *intern, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC) {
    slobel_darray_iterator_data *data = (slobel_darray_iterator_data *) intern->data;

    *int_key = (ulong) data->offset;
    return HASH_KEY_IS_LONG;
}
#endif

static void slobel_darray_iterator_move_forward( zend_object_iterator *intern TSRMLS_DC) {
    slobel_darray_iterator_data *data = (slobel_darray_iterator_data *) intern->data;

    data->offset++;
}

static void slobel_darray_iterator_rewind( zend_object_iterator *intern TSRMLS_DC)
{
    slobel_darray_iterator_data *data = (slobel_darray_iterator_data *) intern->data;

    data->offset = 0;
    data->current = NULL;
}

static zend_object_iterator_funcs slobel_darray_iterator_funcs = {
    slobel_darray_iterator_dtor,
    slobel_darray_iterator_valid,
    slobel_darray_iterator_get_current_data,
    slobel_darray_iterator_get_current_key,
    slobel_darray_iterator_move_forward,
    slobel_darray_iterator_rewind,
    NULL
};

The only remaining thing is to specify the class slobel_darray_init in our get_iterator.

slobel_darray_ce->get_iterator = slobel_darray_get_iterator;
slobel_darray_ce->iterator_funcs.funcs = &slobel_darray_iterator_funcs;

Let's try a test script using foreach:

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

We compile and run our php extension

make && make install
~/dev/bin/php/bin/php -dextension=slobel.so slobel.php

Conclusion

In this article part we added ArrayAccess and Traversable interfaces to our array class that can quickly access any array index entry and consumes an order of magnitude less memory than the standard array in PHP.

A topic not covered in this article is serialization, but for that I advise you to study how to do it properly using the PHP internals book.

Despite the fact that the new PHPNG branch, now known as PHP 7, changed many things, among which greatly optimized the access to arrays, the implementation of internal interfaces for the objects at the time of this writing, has not changed.

And yes, I lied. An array is not a hashtable!

If you want to download the sample code of the article you can find it in this Git repository.

If you liked this article, or you have a question about creating PHP extensions in C, post a comment here now.




You need to be a registered user or login to post a comment

1,611,062 PHP developers registered to the PHP Classes site.
Be One of Us!

Login Immediately with your account on:



Comments:

1. fantastic article - Chris Thomas (2016-06-22 18:46)
I use this article almost every time I am coding PHP extensions... - 0 replies
Read the whole comment and replies



  Blog PHP Classes blog   RSS 1.0 feed RSS 2.0 feed   Blog How to Create a PHP C...   Post a comment Post a comment   See comments See comments (1)   Trackbacks (0)