Argumentos e retornos de funções - Desenvolvendo Extensões PHP Parte 05

No ultimo post eu escrevi sobre como criar funções na sua extensão PHP. Mas o que é uma função se ela não recebe e não retorna um valor? Então nesse post vou falar sobre como definir os parâmetros com ZEND_ARG_INFO, dar o parse nesses parâmetros, e retornar valores.

Apesar de declarar uma função sem as informações de parâmetros gerar um warning, você não precisa declarar essa informação (pelo menos até essa versão do PHP, pois com o warning, pode ser que futuramente isso seja obrigatório). Declarar para o core PHP qual a estrutura de parâmetros e retorno da sua função, ajuda a o core entender e saber o que esperar dela, inclusive manter informações de reflection.

Para declarar qual a estrutura da sua função, declaramos uma estrutura do tipo zend_internal_arg_info, que possui a seguinte estrutura:
typedef struct _zend_internal_arg_info {
 const char *name;
 zend_type type;
 const char *default_value;
} zend_internal_arg_info;
Mas como tudo na Zend Engine são flores, usamos as macros
ZEND_BEGIN_ARG_INFO_EX
e
ZEND_END_ARG_INFO
para definir o bloco, e
ZEND_ARG_INFO
para definir o parâmetro, que preencherá essa estrutura para nós. Nelas e entre elas, devemos declarar se nossa função retorna o valor por referencia, a quantidade de parâmetros obrigatórios e sua definição é:
ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)
Então, vamos usar como exemplo nossa função
myext_hello
. Nela vamos passar um nome, dar "hello" para esse nome. Nosso parâmetro então será o nome. Para defini-lo usamos:
ZEND_BEGIN_ARG_INFO_EX(arginfo_myext_hello, 0, 0, 1)
 ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO();
Isso quer dizer que definimos um argumento chamado
arginfo_myext_hello
, que retornará por valor, e 1 parâmetro obrigatório. Entre o begin e end, temos as definições parâmetro à parâmetro.
ZEND_ARG_INFO
é outra macro, que nos ajuda a definir nosso parâmetro, ela é definida como:
ZEND_ARG_INFO(pass_by_ref, name)
Então aqui definimos se o parâmetro é por valor ou referencia, e o nome do nosso parâmetro.

Caso queira saber como seria isso sem o uso das macros, o código seria algo proximo disso:
static const zend_internal_arg_info arginfo_myext_hello[] = { \
 { (const char*)(zend_uintptr_t)(1), ((void *)0), 0, 0, 0, 0 },
 { "name", ((void *)0), 0, 0, 0, 0 },
};
A ZE nos fornece algumas macros que nos possibilitam tratar praticamente todos os cenários aceitáveis com as macros:
/* Arginfo structures without type information */
#define ZEND_ARG_INFO(pass_by_ref, name)
#define ZEND_ARG_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, default_value)
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name)

/* Arginfo structures with simple type information */
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null)
#define ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, type_hint, allow_null, default_value)
#define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null)

/* Arginfo structures with complex type information */
#define ZEND_ARG_TYPE_MASK(pass_by_ref, name, type_mask, default_value)
#define ZEND_ARG_OBJ_TYPE_MASK(pass_by_ref, name, class_name, type_mask, default_value)

/* Arginfo structures with object type information */
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null)
#define ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, classname, allow_null, default_value)
#define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, classname, allow_null)

/* Legacy arginfo structures */
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null)
#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null)
Caso queira conhecer melhor, basta acessar as definições das macros;

Voltando ao nosso exemplo, agora precisamos informar ao core qual a informação de nossa função
myext_hello
. Para isso, passamos nosso arginfo na declaração da nossa função na estrutura
zend_function_entry
na macro
PHP_FE
:
// Registra as funções para o phpland (mundo php)
static const zend_function_entry ext_functions[] = {
 PHP_FE(myext_hello, arginfo_myext_hello) // Registra a função myext_hello sem parametro
 PHP_FE(myext_test, NULL) // Registra a função myext_test sem parametro
 ZEND_FE_END
};
Nesse ponto, ainda não estamos passando o parâmetro propriamente dito para nossa função. Lembre-se: estamos informando ao core, as informações de nossa função. Se você compilar a extensão e pedir as definições dela, teremos a seguinte informação:
$ php -d extension=./modules/myext.so --re myext

Warning: Missing arginfo for myext_test() in Unknown on line 0
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 ] {
 }
 }
}
Viu? temos duas funções, e na função
myext_hello
, temos as definições dos parâmetros.

Vale deixar registrado, que o uso dos arginfos podem ser compartilhados. Por exemplo, caso tenha uma função que tenha as mesmas informações de parâmetros, você não precisa criar outro arginfo, podendo usar o mesmo
// Registra as funções para o phpland (mundo php)
static const zend_function_entry ext_functions[] = {
 PHP_FE(myext_hello, arginfo_myext_hello) // Registra a função myext_hello sem parametro
 PHP_FE(myext_test, arginfo_myext_hello) // Registra a função myext_test sem parametro
 ZEND_FE_END
};
Recuperando os parâmetros

Se você notar a macro
PHP_FUNCTION
, ela não recebe nenhum parâmetro do phpland. Isso por que o PHP coloca os parâmetros passados em uma pilha, sequencial, e quando você precisa recuperar algum parâmetro, você recupera a sequencia naquela pilha. Você não precisa se preocupar tanto onde esta essa pilha, como ela é organizada, qual a proteção disso, etc. O motor Zend faz tudo isso pra você. Você só precisa saber que existem funções e macros que fazer a captura disso pra você, e essa função é a
zend_parse_parameters()
Sei que é chato essas definições e eu deveria sempre mostrar o código. Mas é preciso entender como as coisas funcionam antes de mostrar algo, por que já já as coisas começam a ficar complicadas. Mas então, para receber o nome na função
myext_hello
, o código seria:
// Cria a função myext_hello
PHP_FUNCTION(myext_hello)
{
 char *name;
 size_t name_len;

 if(zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) { 
 return;
 }
}
É agora que geralmente a mente explode hhahaha. Viu? passamos "s" na função, que significa string, e recebemos name e name_len, só isso. Se precisasse receber 2 valores, string e um double:
char *name;
size_t name_len;
double counter;

if(zend_parse_parameters(ZEND_NUM_ARGS(), "sd", &name, &name_len, &counter) == FAILURE) { 
 return;
}
E se o parâmetro fosse opcional, temos o modificador
|
:
char *name;
size_t name_len;
double counter;

if(zend_parse_parameters(ZEND_NUM_ARGS(), "s|d", &name, &name_len, &counter) == FAILURE) { 
 return;
}
Voltando ao nosso exemplo, ja temos o nome, vamos montar a string "Hello " + name. Já temos o
char*
, então agora é só programar:
// Cria a função myext_hello
PHP_FUNCTION(myext_hello)
{
 char *name;
 size_t name_len;

 // Recupera o parametro do tipo String
 if(zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) { 
 return;
 }

 // Cria a string final
 char *my_hello;
 my_hello = malloc(name_len+5); // aloca o tamanho de name + 6 chars "hello "
 strcpy(my_hello, "Hello "); // Copia o inicio
 strcat(my_hello, name); // Concatena o nome

 printf("%s\n", my_hello); // Printa na tela
}
Talvez os dinossauros possam achar meu código complexo de mais para o que foi feito, mas não sou nenhum expert em C, e também se lembre que isso é um tutorial, principalmente de quem está vindo do PHP. Então essa foi a forma mais intuitiva que achei para concatenar uma string em C.

Então veja o quanto é simples. Veja também que usei o printf para mostrar na tela a string. Sua extensão já deve poder ser compilada. Então compile e mude o nosso arquivo
test.php
para:
<?php
 myext_hello("And Or");
Se rodarmos nosso teste, a saída deve ser:
$ php -d extension=./modules/myext.so test.php 
Hello And Or
Você vai se perguntar, como saber os tipos, e por que o parametro do tipo "s" precisa de 2 variaveis. Isso é definido no fonte do do próprio PHP como:
a - array (zval*)
A - array or object (zval*)
b - boolean (zend_bool)
C - class (zend_class_entry*)
d - double (double)
f - function or array containing php method call info (returned as zend_fcall_info and zend_fcall_info_cache)
h - array (returned as HashTable*)
H - array or HASH_OF(object) (returned as HashTable*)
l - long (zend_long)
L - long, limits out-of-range numbers to LONG_MAX/LONG_MIN (zend_long, ZEND_LONG_MAX/ZEND_LONG_MIN)
o - object of any type (zval*)
O - object of specific type given by class entry (zval*, zend_class_entry)
p - valid path (string without null bytes in the middle) and its length (char*, size_t)
P - valid path (string without null bytes in the middle) as zend_string (zend_string*)
r - resource (zval*)
s - string (with possible null bytes) and its length (char*, size_t)
S - string (with possible null bytes) as zend_string (zend_string*)
z - the actual zval (zval*)
* - variable arguments list (0 or more)
+ - variable arguments list (1 or more)
Veja o tipo
s
é bem definido como
(char*, size_t)
, então sabemos quais parâmetros passar após a definição dos tipos

Alternativamente, você pode usar macros para recuperar os parâmetros, usando o bloco
ZEND_PARSE_PARAMETERS_START
e
ZEND_PARSE_PARAMETERS_END
:
// Cria a função myext_hello
PHP_FUNCTION(myext_hello)
{
 char *name;
 size_t name_len;

 // Recupera os parametros
 ZEND_PARSE_PARAMETERS_START(1, 1)
 Z_PARAM_STRING(name, name_len)
 ZEND_PARSE_PARAMETERS_END();

 char *my_hello;
 my_hello = malloc(name_len+5); // aloca o tamanho de name + 6 chars "hello "
 strcpy(my_hello, "Hello ");
 strcat(my_hello, name);

 printf("%s\n", my_hello);
}
Não vou negar, que prefiro esse formato. É mais limpo, mais organizado. A lista de macros para recuperar os parâmetros é:
Z_PARAM_ARRAY() /* old "a" */
Z_PARAM_ARRAY_OR_OBJECT() /* old "A" */
Z_PARAM_BOOL() /* old "b" */
Z_PARAM_CLASS() /* old "C" */
Z_PARAM_DOUBLE() /* old "d" */
Z_PARAM_FUNC() /* old "f" */
Z_PARAM_ARRAY_HT() /* old "h" */
Z_PARAM_ARRAY_OR_OBJECT_HT() /* old "H" */
Z_PARAM_LONG() /* old "l" */
Z_PARAM_STRICT_LONG() /* old "L" */
Z_PARAM_OBJECT() /* old "o" */
Z_PARAM_OBJECT_OF_CLASS() /* old "O" */
Z_PARAM_PATH() /* old "p" */
Z_PARAM_PATH_STR() /* old "P" */
Z_PARAM_RESOURCE() /* old "r" */
Z_PARAM_STRING() /* old "s" */
Z_PARAM_STR() /* old "S" */
Z_PARAM_ZVAL() /* old "z" */
Z_PARAM_VARIADIC() /* old "+" and "*" */
E para parâmetros opcionais, temos
Z_PARAM_OPTIONAL
:
char *name;
size_t name_len;
double counter;

ZEND_PARSE_PARAMETERS_START(1, 2)
 Z_PARAM_STRING(name, name_len)
 Z_PARAM_OPTIONAL
 Z_PARAM_DOUBLE(counter)
ZEND_PARSE_PARAMETERS_END();
E claramente você pode querer saber se o parâmetro opcional foi passado, dai usamos o sufixo
_OR_NULL
e passamos a variável teste:
char *name;
size_t name_len;
double counter;
bool counter_is_null;

ZEND_PARSE_PARAMETERS_START(1, 2)
 Z_PARAM_STRING(name, name_len)
 Z_PARAM_OPTIONAL
 Z_PARAM_DOUBLE_OR_NULL(counter, counter_is_null)
ZEND_PARSE_PARAMETERS_END();

Retornando valores
PHP_FUNCTION
não retorna valor algum, ela retorna um
void
. Se expandirmos a macro, veremos algo assim:
void zif_myext_hello(zend_execute_data *execute_data, zval *return_value) 
{ 
 / * código * / 
}
Notou que ele retorna um
void
? Notou também o ponteiro
return_value
? Pois é, então o
PHP_FUNCTION
não retorna valor algum, mas temos algumas macros que farão o retorno em
return_value
. Então para retornar nossa string ao invés de usar o
printf
, fazemos:
// Cria a função myext_hello
PHP_FUNCTION(myext_hello)
{
 char *name;
 size_t name_len;
 
 ZEND_PARSE_PARAMETERS_START(1, 1)
 Z_PARAM_STRING(name, name_len)
 ZEND_PARSE_PARAMETERS_END();
 
 char *my_hello;
 my_hello = malloc(name_len+5); // aloca o tamanho de name + 6 chars "hello "
 strcpy(my_hello, "Hello ");
 strcat(my_hello, name);
 
 RETURN_STRING(my_hello);
}
Aqui podemos compilar nossa extensão novamente, e agora nossa função myext_hello deverá retornar a string:
<?php
 $hello = myext_hello("And Or");
 var_dump($hello);
E temos a saída:
$ php -d extension=./modules/myext.so test.php 
string(12) "Hello And Or"

Para os retornos, temos varias macros, que retorna todos os tipos comuns, nas seguintes estruturas::
#define RETURN_BOOL(b)
#define RETURN_NULL() 
#define RETURN_LONG(l)
#define RETURN_DOUBLE(d)
#define RETURN_STR(s)
#define RETURN_INTERNED_STR(s)
#define RETURN_NEW_STR(s)
#define RETURN_STR_COPY(s)
#define RETURN_STRING(s)
#define RETURN_STRINGL(s, l)
#define RETURN_STRING_FAST(s)
#define RETURN_STRINGL_FAST(s, l)
#define RETURN_EMPTY_STRING() 
#define RETURN_CHAR(c)
#define RETURN_RES(r)
#define RETURN_ARR(r)
#define RETURN_EMPTY_ARRAY() 
#define RETURN_OBJ(r)
#define RETURN_OBJ_COPY(r)
#define RETURN_COPY(zv)
#define RETURN_COPY_VALUE(zv)
#define RETURN_ZVAL(zv, copy, dtor)
#define RETURN_FALSE 
#define RETURN_TRUE 
#define RETURN_THROWS() 
Então nosso código exemplo, com a função myext_hello implementada até aqui é:
/**
 * Inclui as libs necessárias
 */
#ifdef HAVE_CONFIG_H
 #include "config.h"
#endif
#include "php.h"
#include "ext/standard/info.h"


/**
 * Declarações globais
 */
ZEND_BEGIN_MODULE_GLOBALS(myext)

// Minhas variaveis globais
ZEND_END_MODULE_GLOBALS(myext)

ZEND_DECLARE_MODULE_GLOBALS(myext)

/**
 * Inicialização dos globais
 */
PHP_GINIT_FUNCTION(myext)
{
 ZEND_SECURE_ZERO(myext_globals, sizeof(*myext_globals));
}

/**
 * Função chamada quando o processamento é iniciado
 */
PHP_RINIT_FUNCTION(myext)
{
 return SUCCESS;
}

/**
 * Função executada ao iniciar a extensão
 */
PHP_MINIT_FUNCTION(myext)
{
 return SUCCESS;
}

/**
 * Função executada ao finalizar a extensão
 */
PHP_MSHUTDOWN_FUNCTION(myext)
{
 return SUCCESS;
}

/**
 * Função executada ao finalizar o processamento
 */
PHP_RSHUTDOWN_FUNCTION(myext)
{
 return SUCCESS;
}

/**
 * Finalização dos globais
 */
PHP_GSHUTDOWN_FUNCTION(myext)
{
}

/**
 * Função chamada quando alguem pede alguma informação da extensão
 */
PHP_MINFO_FUNCTION(myext)
{
 php_info_print_table_start();
 php_info_print_table_header(2, "myext support", "enabled");
 php_info_print_table_end();
}

// Declara a descrição dos argumentos
ZEND_BEGIN_ARG_INFO_EX(arginfo_myext_hello, 0, 0, 1)
 ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO();


// Cria a função myext_hello
PHP_FUNCTION(myext_hello)
{
 char *name;
 size_t name_len;

 ZEND_PARSE_PARAMETERS_START(1, 1)
 Z_PARAM_STRING(name, name_len)
 ZEND_PARSE_PARAMETERS_END();

 char *my_hello;
 my_hello = malloc(name_len+5); // aloca o tamanho de name + 6 chars "hello "
 strcpy(my_hello, "Hello ");
 strcat(my_hello, name);

 RETURN_STRING(my_hello);
}


// Cria a função myext_test
PHP_FUNCTION(myext_test)
{

}

// Registra as funções para o phpland (mundo php)
static const zend_function_entry ext_functions[] = {
 PHP_FE(myext_hello, arginfo_myext_hello) // Registra a função myext_hello sem parametro
 PHP_FE(myext_test, arginfo_myext_hello) // Registra a função myext_test sem parametro
 ZEND_FE_END
};

/**
 * Configuração da nossa extensão
 */
extern zend_module_entry myext_module_entry;
zend_module_entry myext_module_entry = {
 STANDARD_MODULE_HEADER, // Campos size, zend_api, zend_debug, zts, ini_entry e deps
 "myext", // Nome da extensão
 ext_functions, // Funções da nossa extensão
 PHP_MINIT(myext), // Função executada ao iniciar a extensão
 PHP_MSHUTDOWN(myext), // Função executada ao finalizar a extensão
 PHP_RINIT(myext), // Função executada ao iniciar o processamento
 PHP_RSHUTDOWN(myext), // Função executada ao finalizar o processamento
 PHP_MINFO(myext), // Função executada quando alguem pede informações da extensão
 
 "0.0.1", // Versão da extensão

 PHP_MODULE_GLOBALS(myext), // Macro para o tamanho da estrutura globais
 PHP_GINIT(myext), // Inicializa os globais
 PHP_GSHUTDOWN(myext), // Finaliza os globais,

 NULL, // Função executada após-finalização da finalização do request
 STANDARD_MODULE_PROPERTIES_EX // Campos globais e de controle interno
};

/**
 * Simbolo chamado pelo core
 */
ZEND_GET_MODULE(myext)

Existe uma tabela chamada hashtables, que é o terror e o amor de quem desenvolve extensão. É essa tabela que nos ajuda a ter um PHP maravilhoso como ele é, é o que define os arrays. Próximo post vou falar um pouco mais dele

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: http://www.phpinternalsbook.com/php7/extensions_design/php_functions.html, https://github.com/php/php-src/blob/master/Zend/zend_API.h http://ftp.psu.ac.th/pub/php/html/zendapi-reference.html