No post anterior trabalhamos com parâmetros e retornos de uma extensão PHP, porem faltou falar um pouco sobre um recurso poderoso do PHP, que são os vetores. Trabalhar com vetores em C na mesma maneira que PHP pode ser algo extremamente complexo, pois temos índices numéricos e literais, de tipos diferentes no mesmo vetor, e graças a API Zend, isso se torna um trabalho tranquilo.

A primeira coisa que temos que fazer, é declarar nossas informações de argumento, e para isso usamos o ZEND_ARG_ARRAY_INFO:

ZEND_BEGIN_ARG_INFO_EX(arginfo_myext_test, 0, 0, 1)
    ZEND_ARG_ARRAY_INFO(0, vetor, 0)
ZEND_END_ARG_INFO();

Assim, dizemos ao nosso core que a função receberá um vetor como parâmetro:

$ /opt/php8/bin/php -d extension=./modules/myext.so --re myext
Extension [ <persistent> extension #9 myext version 0.0.1 ] {

  - Functions {
    Function [ <internal:myext> function myext_hello ] {

      - Parameters [1] {
        Parameter #0 [ <required> $name ]
      }
    }
    Function [ <internal:myext> function myext_test ] {

      - Parameters [1] {
        Parameter #0 [ <required> array $vetor ]
      }
    }
  }
}

Note que a função myext_test já está recebendo um parâmetro do tipo array, agora falta receber o parâmetro dentro de nossa função, e para isso usamos o Z_PARAM_ARRAY:

zval *vetor = NULL;

ZEND_PARSE_PARAMETERS_START(1, 1)
    Z_PARAM_ARRAY(vetor)
ZEND_PARSE_PARAMETERS_END();

Caso precise relembrar as macros, não deixe de buscar as referencias no post anterior.

Notou que colocamos nosso vetor numa variável do tipo zval? é aqui que o doce azeda, isso por que essa estrutura é responsável pela baixa tipagem do PHP. Ela armazena o nome, tipo e o valor em cada campo correspondente ao seu tipo.

Entendendo o zval

Basicamente é uma estrutura muito simples e muito compreensível, o que complica é que é muito fácil de se perder nessa estrutura. Ela é uma estrutura que possui o valor definida em outra estrutura chamada zend_value, e elas são definidas resumidamente como:

typedef union _zend_value {
    zend_long         lval;             /* long value */
    double            dval;             /* double value */
    zend_refcounted  *counted;
    zend_string      *str;
    zend_array       *arr;
    zend_object      *obj;
    zend_resource    *res;
    zend_reference   *ref;
    zend_ast_ref     *ast;
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

typedef struct _zval_struct {
    zend_value     value;
    zend_uchar     type;
    zend_uchar     type_flags;
    uint16_t       extra;
    uint32_t       reserved;
} zval;

Apesar de parecer complexa, veja que é bem simples. zval define o tipo, flags necessários e a estrutura do valor. Então se zval->type == IS_LONG, zend_value retornará o campo lval. Se o zval->type == IS_STRING, o zval->value retornará o campo *str. Não tem nada de complicado certo?

Então para trabalharmos com array, cada posição do vetor é um zval, assim cada posição pode ser de um tipo.

O que azeda, pelo menos para mim, é que a cada versão zval fica mais simples de ser manipulado, e as referencias que procuramos geralmente são antigas e complexas. zval é uma estrutura linda, muito bem pensada e definida, mas a documentação do internals é muito vaga, e os escritores escrevem em seu tempo, tornando as referencias antigas. Por exemplo, esse meu texto pode ser muito complexo quando existir o PHP 9 ou 10.

Como você notou, podemos comparar o tipo do zval usando a macro Z_TYPE:

if(Z_TYPE(zval_var) == IS_STRING)  {

}
else if(Z_TYPE(zval_var) == IS_LONG)  {

}

Temos as seguintes constantes definidas:

IS_UNDEF
IS_NULL
IS_FALSE
IS_TRUE
IS_LONG
IS_DOUBLE
IS_STRING
IS_ARRAY
IS_OBJECT
IS_RESOURCE
IS_REFERENCE
IS_CONSTANT_AST
IS_CALLABLE
IS_ITERABLE
IS_VOID
IS_STATIC
IS_MIXED

E para receber o valor, devemos usar as macros, algo como:

if(Z_TYPE(zval_var) == IS_STRING)  {
    php_printf("Valor: %s\n", Z_STRVAL(zval_var));
}
else if(Z_TYPE(zval_var) == IS_LONG)  {
    php_printf("Valor: %ld\n", Z_LVAL(zval_var));
}

usamos Z_STRVAL e Z_LVAL nos exemplos, mas temos as macros:

Z_LVAL
Z_DVAL
Z_STRVAL
Z_STRLEN
Z_STRHASH
Z_ARRVAL
Z_OBJ
Z_OBJ_HANDLER
Z_OBJCE
Z_RES
Z_RES_HANDLE
Z_RES_TYPE
Z_REFVAL
Z_FUNC
Z_PTR

Toda macro que trabalha com zval possui macros correspondentes para ponteiros com sufixo _P, então trabalhamos assim:

// Sem ponteiro
zval var1;
if(Z_TYPE(var1) == IS_STRING)  {
    php_printf("valor: %s\n", Z_STRVAL(var1));
}

// Com ponteiro
zval *var2;
if(Z_TYPE_P(var2) == IS_STRING)  {
    php_printf("valor: %s\n", Z_STRVAL_P(var2));
}

Então se a gente quiser percorrer todos os valores de nosso vetor, fazemos um for nas posições:

// Cria a função myext_test
PHP_FUNCTION(myext_test)
{
    zval *vetor = NULL;

    // Recupera os parametros
    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_ARRAY(vetor)
    ZEND_PARSE_PARAMETERS_END();


    int vetor_size = (int)zend_array_count(Z_ARRVAL_P(vetor));

    php_printf("Tamanho do vetor: %d\n", vetor_size);

    for (int i = 0; i < vetor_size; i++) {
        zval *vetor_elemento = zend_hash_index_find_deref(Z_ARRVAL_P(vetor), i);

        // Valor
        if(Z_TYPE_P(vetor_elemento) == IS_STRING)  {
            php_printf("Valor: %s\n", Z_STRVAL_P(vetor_elemento));
        }
        else if(Z_TYPE_P(vetor_elemento) == IS_LONG)  {
            php_printf("Valor: %ld\n", Z_LVAL_P(vetor_elemento));
        }
    }
}

Podemos testar nossa função com o código:

<?php

myext_test(["valor 1", "valor 2", 10]);

E teremos o resultado:

$ /opt/php8/bin/php -d extension=./modules/myext.so test.php 
Tamanho do vetor: 3
Valor: valor 1
Valor: valor 2
Valor: 10

Esse código não vai funcionar se a gente tiver uma string como index. Veja que no meu exemplo, as posições do vetor são 0, 1 e 2. Não precisamos tratar quando o vetor é uma string ou um long, pois a Zend Engine novamente nos ajuda nessa tarefa:

// Cria a função myext_test
PHP_FUNCTION(myext_test)
{
    zval *vetor = NULL;

    // Recupera os parametros
    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_ARRAY(vetor)
    ZEND_PARSE_PARAMETERS_END();

    // Mostra o tamanho do vetor
    php_printf("Tamanho do vetor: %d\n\n", zend_hash_num_elements(Z_ARR_P(vetor)));

    // Faz um foreach no vetor
    ZEND_HASH_FOREACH_KEY_VAL(Z_ARR_P(vetor), zend_ulong long_key, zend_string *str_key, zval *val)

        // Verifica se o index é uma string ou um long
        if(!str_key) {
            php_printf("Index: %ld\n", long_key);
        }
        else {
            php_printf("Index: %s\n", str_key->val);
        }

        // Printa o valor dependendo do tipo
        if(Z_TYPE_P(val) == IS_STRING)  {
            php_printf("Valor: %s\n", Z_STRVAL_P(val));
        }
        else if(Z_TYPE_P(val) == IS_LONG)  {
            php_printf("Valor: %ld\n", Z_LVAL_P(val));
        }

        php_printf("\n");

    ZEND_HASH_FOREACH_END();
}

Notou as macros ZEND_HASH_FOREACH_KEY_VAL e ZEND_HASH_FOREACH_END? Essas belezinhas nos deixam trabalhar exatamente como no PHP, recebendo o index se for long ou string, e o valor do index. Se a gente testar agora com o código:

<?php

myext_test(["valor 1", "valor 2", 'Meu Index'=>10]);

Temos a seguinte saída:

$ /opt/php8/bin/php -d extension=./modules/myext.so test.php 
Tamanho do vetor: 3

Index: 0
Valor: valor 1

Index: 1
Valor: valor 2

Index: Meu Index
Valor: 10

Próximo post quero fazer mais alguns exemplo de situações reais com vetores antes de passar a trabalhar com objetos, por que trabalhar com vetor é algo muito comum e muito fácil de se perder.

Pessoal, gostaria de deixar um apelo ai para darem uma força. compartilhem, comentem, deixe seu comentário no post. quem escreve ou faz vídeo, muitas vezes nem faz isso por dinheiro, mas sim para engajar a comunidade de alguma forma. não gostou da forma que eu escrevo? comenta na boa (nem eu gosto também), mas comenta algo, manda pro brodi, bora crescer e centralizar um conteúdo de qualidade

Referencias: https://github.com/php/php-src/blob/master/Zend/zend_types.h, https://github.com/php/php-src/blob/master/Zend/zend_API.h https://github.com/php/php-src/blob/master/Zend/zend.h, https://github.com/php/php-src/blob/master/Zend/zend_hash.h