Back-End

9 mai, 2016

Como criar uma extensão PHP C para manipular arrays – Parte 02: adicionando interfaces ArrayAccess e Traversable

Publicidade

Na primeira parte deste artigo, aprendemos como criar uma extensão para PHP escrita em C para criar uma classe que funciona como array. No entanto, para fazer com que os objetos da classe realmente se comportem como arrays, você precisa implementar certas interfaces na classe.

Leia este artigo para aprender como fazer uma classe PHP definida por uma extensão C implementar interfaces ArrayAccess e Traversable, e entender como resolver problemas que você pode encontrar que fazem a sua extensão ficar mais lenta do que o esperado.

Introdução

Na primeira parte deste artigo, nós aprendemos como podemos criar extensões PHP escritas em C e, eventualmente, executar tarefas com fins específicos muito mais rápido do que conseguiríamos usando código puro PHP.

O artigo explica como definir uma classe que pode armazenar dados em arrays.

Esta parte dá continuidade ao artigo anterior, explicando como você pode implementar certas interfaces. Nesse caso, uma vez que queremos que nossos objetos de classe se comportem como arrays, precisamos implementar interfaces como ArrayAccess e Traversable.

Adicionando a interface ArrayAccess

Vamos adicionar primeiro as funções necessárias para implementar a interface ArrayAccess.

No arquivo ds/darray.h, adicione as funções declarativas para trabalhar com uma array: get, set, unset e 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);

Essas funções são implementadas em ds/darray.с:

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

#define ELEM_SIZE (sizeof(zval))

// Aumentar a memória para os elementos do array. 
// Se um número de índice é especificado, você aloca memória com capacidade para armazenar os elementos até o elemento com o número do índice
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 copiando apenas um valor zval para todos os elementos, mas eles apontam para o mesmo valor.
    //Para corrigir isso, você precisa checar todos os elementos e chamar a função 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;
    }

    // De qualquer forma, certifique-se de que 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;
}

Como você pode notar em slobel_ds_darray_set, nós não utilizaremos o ALLOC_ZVAL, mas sim um bloco de memória alocado anteriormente. No nosso caso, é importante que os elementos do array estejam contínuos na memória. Além disso, os elementos do array não são definidos diretamente no código, de modo que a garbage collection não é necessária. Para remover os elementos, nós usamos zval_dtor o zval_ptr_dtor.

Agora, vamos começar a usar os novos recursos de função implementados pela interface ArrayAccess.

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) {
        //o que fazer, a fonte, a causa zval_copy_ctor, a causa 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);
    }
}

Vamos adicionar os recursos das funções à tabela de função na classe.

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

Agora, nós declaramos que a nossa classe PHP implementa a interface ArrayAccess.

zend_class_implements(slobel_darray_ce TSRMLS_CC, 1, zend_ce_arrayaccess);

O último parâmetro na chamada de função é o número de interfaces, e as interfaces class_entry são separadas por vírgulas.

zend_ce_arrayaccess declarada no arquivo zend_interfaces.h (com zend_ce_traversable, zend_ce_aggregate, zend_ce_iterator e zend_ce_serializable), que precisamos incluir no arquivo slobel_darray.c.

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

Nós escrevemos o código de teste e, ao mesmo tempo, ele é comparável com a nossa classe com o array habitual:

<?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();

?>

Vamos compilar e executar a nossa extensão PHP:

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

Ei, espere um minuto, nosso código acabou sendo mais lento do que o array padrão do PHP!

Personalizando os Class Object Handlers

Vamos ver por que o nosso array acabou ficando tão lento. Para fazer isso, lembre-se do object_handlers, que foram mencionados anteriormente.

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

Para trabalhar com um objeto como um array, use as seguintes funções: read_dimension, write_dimension, has_dimension e unset_dimension.

Se olharmos para o código de zend_std_read_dimension, vemos que há uma verificação para a interface ArrayAccess e uma chamada para a função de classe offsetGet. Essa é uma chamada para uma função PHP, quero dizer, uma operação muito, muito lenta!

A solução para isso deveria ser óbvia: escrever a função para o recurso read_dimension, bem como count e clone.

slobel_darray.c:

// Função auxiliar que leva um 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);

    // Copiar as propriedades de um objeto.
    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;
    }
}

É interessante notar a função slobel_darray_read_dimension, que é o terceiro parâmetro que tem um tipo inteiro. Essa é uma flag que indica o contexto no qual a função foi chamada, e pode ter os valores BP_VAR_R, BP_VAR_W, BP_VAR_RW, BP_VAR_IS ou 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

Se ignorarmos o tipo e sempre retornar uma cópia do valor, no segundo caso acima não acontece nada, e o valor do array dentro do array não muda. Para corrigir isso, se dermos o valor BP_VAR_W diretamente no array para que o garbage collector não tente removê-lo, nós colocamos zval-> is_ref__gc = 1 (isso é um hack).

Em cada função, vamos verificar a disponibilidade (intern-> std.ce-> parent). Isso acontece apenas no caso de alguém estender a nossa classe e substituir o método ArrayAccess.

Para usar a nossa função PHP em vez da padrão, adicione o slobel_darray_init com as seguintes linhas.

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;

Agora vamos compilar e executar o PHP com a nossa extensão.

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

Agora podemos ver que o consumo de memória é menor e que o desempenho da aplicação quase duplicou em comparação com os resultados anteriores. Sucesso!

Adicionando a interface Traversable

Para que a nossa classe se torne um array, você precisa fazê-la ser iterable. Você precisa adicionar funções de iteração object_handlers mas em zend_class_entry há apenas a função get_iterator e de estrutura iterator_funcs.

get_iterator retorna zend_object_iterator, que é usado para iterar sobre os elementos do array usando, por exemplo, a função foreach do PHP.

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

Com iterator_funcs, no meu entendimento, você precisa usar código personalizado: a classe que implementa a interface Iterator ou IteratorAggregate. Campos zf_ * – (cache?) são funções PHP personalizadas. Um campo de funcs semelhante é o zend_object_iterator. Seria bom se nos comentários do código-fonte do PHP alguém desse uma explicação completa de como iterator_funcs deve ser usado.

No arquivo slobel_darray.c, depois de definir a estrutura slobel_darray, adicione uma estrutura para armazenar dados relevantes à iteração.

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;

Agora escreva uma função get_iterator. Em slobel_darray.c, após a função count_elements, adicione a função slobel_darray_get_iterator.

//by_ref - uma flag indicando o valor do link solicitado.
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;
}

E, em seguida, o código de função para a iteração. A fim de evitar declará-los separadamente, adicione a eles a função 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
// Em versões anteriores string e chaves numéricas deveriam ser dadas a variáveis individuais
// e retornar 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
};

 

A única coisa que resta é especificar a classe slobel_darray_init em nosso get_iterator.

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

Vamos tentar um script de teste usando foreach:

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

Nós compilamos e executamos a nossa extensão php:

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

Conclusão

Nesta parte do artigo, nós adicionamos interfaces ArrayAccess e Traversable em nossa classe array que pode acessar rapidamente qualquer entrada índice do array e consome muito menos memória do que o array padrão do PHP.

Um tema não abordado neste artigo é a serialização, mas para isso eu aconselho você a estudar a forma de fazê-la corretamente usando o PHP internals book.

Apesar de o novo branch PHPNG, agora conhecido como PHP 7, ter mudado muitas coisas, entre as quais uma grande otimização no acesso a arrays, a implementação de interfaces internas para os objetos no momento da escrita deste texto não mudou.

E sim, eu menti. Um array não é um hashtable!

Se você quiser fazer o download do código de exemplo do artigo, você pode encontrá-lo neste repositório do GitHub.

***

Dmitry Mamontov faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://www.phpclasses.org/blog/post/306-How-to-Create-a-PHP-C-Extension-to-Manipulate-Arrays-Part-2-Adding-ArrayAccess-and-Traversable-interfaces.html