Entendendo a estrutura zval e vetores - Desenvolvendo Extensões PHP Parte 06
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
Notou que colocamos nosso vetor numa variável do 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
Então para trabalharmos com
O que azeda, pelo menos para mim, é que a cada versão
Como você notou, podemos comparar o tipo do
usamos
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_testjá 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. zvaldefine o tipo, flags necessários e a estrutura do valor. Então se
zval->type == IS_LONG,
zend_valueretornará o campo
lval. Se o
zval->type == IS_STRING, o
zval->valueretornará 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
zvalfica 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
zvalusando 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_STRVALe
Z_LVALnos 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 zvalpossui 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_VALe
ZEND_HASH_FOREACH_END? Essas belezinhas nos deixam trabalhar exatamente como no PHP, recebendo o index se for
longou
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.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.hPessoal, 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