PHP extension: convert an object to string with __toString() - php

Writing a PHP extension in C, I want to convert a userland object (IS_OBJECT) to a string through __toString() if it has one, and fail otherwise. What should I use?
I don't need another zval on output, just a char *.
zval *zo;
switch (Z_TYPE_P(zo)) {
case IS_STRING:
... Z_STRVAL_P(zo) ...
break;
case IS_OBJECT:
... ???(zo) ...
break;
...
}

The reflection module does something like
ZVAL_STRINGL(&fname, "__tostring", sizeof("__tostring") - 1, 1);
result= call_user_function_ex(NULL, &object, &fname, &retval_ptr, 0, NULL, 0, NULL TSRMLS_CC);
zval_dtor(&fname);
if (result == FAILURE) {
_DO_THROW("Invocation of method __toString() failed");
/* Returns from this function */
}
And then you'd extract the char* with Z_STRVAL_P().
But I guess you could also use
case IS_OBJECT:
if ( SUCCESS==zend_std_cast_object_tostring(uservar, uservar, IS_STRING TSRMLS_CC) ) {
int len = Z_STRLEN_P(uservar);
char* pValue = Z_STRVAL_P(uservar);
...
}
zend_std_cast_object_tostring() is implemented in zend/zend_object_handlers.c. You might want to check if it really does what you want

Related

var_dump(object) results "*RECURSION*"

I'm write simple extension with class definition
extension.h
zend_class_entry * ExampleClass_class;
zend_class_entry * get_ExampleClass_class();
extension.c
#include "php.h"
#include "extension.h"
...
zend_class_entry * get_ExampleClass_class(){
return ExampleClass_class;
}
....
PHP_METHOD(ExampleClass, getInstance){
ZEND_PARSE_PARAMETERS_START(0, 0)
Z_PARAM_OPTIONAL
ZEND_PARSE_PARAMETERS_END();
RETURN_OBJ(
// ----------- fun objectToZval(obj: PhpObject) = obj.zval //CPointer<zval>
example_symbols()->kotlin.root.php.extension.proxy.objectToZval(
example_symbols()->kotlin.root.exampleclass.getInstance(
// ------- Unused parameter
example_symbols()
->kotlin.root.php.extension.proxy.phpObj(
ExampleClass_class, getThis()
)
// ------- Unused parameter end
)
)
)
}
Also I write and compile static library with logic realization (Kotlin Native)
.def
static inline zval* zend_helper_new_ExampleClass() {
zval *obj = malloc(sizeof(zval));
object_init_ex(obj, get_ExampleClass_class());
return obj;
}
.kt
fun newExampleClass() = zend_helper_new_ExampleClass()!!
//PhpObject is wrapper for two fields CPointer<zend_class_entry> and CPointer<zval>
class PhpObject(val context: CPointer<zend_class_entry>, val zval: PhpMixed) {
companion object {
fun fromMixed(zval: PhpMixed) = PhpObject(zval.pointed!!.value.obj!!.pointed!!.ce!!, zval)
}
....
}
val PhpMixed.phpObject get() = PhpObject.fromMixed(this)
fun getInstance(obj: PhpObject) = newExampleClass().phpObject
Finally I run PHP code
var_dump(ExampleClass::getInstance());
And receive this
# /opt/rh/rh-php71/root/usr/bin/php -dextension=`ls ./phpmodule/modules/*.so` -r "var_dump(ExampleClass::getInstance());"
*RECURSION*
#
Where I mistaken?
UPD
static inline zval* zend_helper_new_{className}() {
zval *obj = malloc(sizeof(zval));
object_init_ex(obj, get_{className}_class());
php_printf("Just created FLAGS %u\n", GC_FLAGS(obj->value.obj));
return obj;
}
Just created object have GC_FLAGS equals 0
*RECURSIVE* apears in function php_var_dump by code
case IS_OBJECT:
if (Z_IS_RECURSIVE_P(struc)) {
PUTS("*RECURSION*\n");
return;
}
Macro->macro->macro->Oh god!->macro->macro...
Z_IS_RECURSIVE_P(struc) = (GC_FLAGS((*(zval)).value.counted) & GC_PROTECTED)
Okay...
php_printf("%d\n", GC_FLAGS((*(obj)).value.counted));
Returns 0
Must not trigger *RECURSIVE*, but... Why!?
First
For compilation I used PHP 7.1.8, but coding based on latest sources.
Recursion protection has been changed 06.10.2017
Actual var_dump code for 7.1.8
case IS_OBJECT:
if (Z_OBJ_APPLY_COUNT_P(struc) > 0) {
PUTS("*RECURSION*\n");
return;
}
But it doesn't matter
Second
RETURN_OBJ(
example_symbols()->kotlin.root.php.extension.proxy.objectToZval(
example_symbols()->kotlin.root.exampleclass.getInstance(/*unused*/)
)
)
Let's expand the macro RETURN_OBJ (r)
RETURN_OBJ(r)
{ RETVAL_OBJ(r); return; }
{ ZVAL_OBJ(return_value, r); return; }
.
{ do {
zval *__z = (return_value);
Z_OBJ_P(__z) = (r);
Z_TYPE_INFO_P(__z) = IS_OBJECT_EX;
} while (0); return; }
.
{ do {
zval *__z = (return_value);
Z_OBJ(*(__z)) = (r);
Z_TYPE_INFO(*(__z)) = (IS_OBJECT | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT));
} while (0); return; }
.
{ do {
zval *__z = (return_value);
(*(__z)).value.obj = (r);
(*(__z)).u1.type_info = (8 | ((1<<0) << 8));
} while (0); return; }
You see? :)
Yea, this macro must receive zend_object but not zval
Just change return expression to
example_symbols()->kotlin.root.php.extension.proxy.zendObject(
example_symbols()->kotlin.root.exampleclass.getInstance(/*unused*/)
)
where
fun zendObject(obj: PhpObject) = obj.zval.pointed!!.value.obj!!
Bingo!
PS Special thanks for php developers community for incredible documented macro hell

how to convert zval to vector for php extension?

i'm writing a php extension for my c++ library which is defined something like this:
bool getPids(map<string,string> pidsMap, vector<string> ids);
now, i'm writing a php wrapper for above function like this.
ZEND_METHOD(myFInfo, get_pids)
{
zval *idsArray;
if (zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "a",
&idsArray ) == FAILURE )
{
RETURN_NULL();
}
}
now i want to call getPids(), but i don't know the proper way to pass idsArray as vector into the c++ function.
after some search on web, i found an example where zval array is iterated to read each values, and i thought maybe i can use this to create a vector.
PHP_FUNCTION(hello_array_strings)
{
zval *arr, **data;
HashTable *arr_hash;
HashPosition pointer;
int array_count;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) == FAILURE) {
RETURN_NULL();
}
arr_hash = Z_ARRVAL_P(arr);
array_count = zend_hash_num_elements(arr_hash);
php_printf("The array passed contains %d elements", array_count);
for(zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
zend_hash_move_forward_ex(arr_hash, &pointer)) {
if (Z_TYPE_PP(data) == IS_STRING) {
PHPWRITE(Z_STRVAL_PP(data), Z_STRLEN_PP(data));
php_printf("
");
}
}
RETURN_TRUE;
}
but is this the best approach? or is there a better way to do this?
thanks!
To populate a std::vector<std::string> from a PHP array, here's how I'd do it (the short version):
std::vector<std::string> vec;
HashTable *arr_hash = Z_ARRVAL_P(arr);
zval **arr_value;
for(zend_hash_internal_pointer_reset(arr_hash);
zend_hash_get_current_data(arr_hash, (void **)&arr_value) == SUCCESS;
zend_hash_move_forward(arr_hash))
{
vec.push_back(Z_STRVAL_PP(arr_value));
}
...where arr is your input zval *, and vec is your output vector.

How does array_keys do the search for value?

How does PHP array_keys do the search for value?
Example:
$array2 = array("xyz", "xyz", "abc", "abc", "xyz", "xyz", "text", "abc", "text");
print_r(array_keys($array2,"abc"));
Since they are key,value pairs. I am guessing PHP to do a search based on hash rather than iterate through each of the key-pairs in the array.
Any 'clarity thoughts' on this?
Question inspired by this question: How to get the keys of empty elements in an array if the corresponding element in a similar-sized array is a number (without iteration)?
In the php source, they iterate through each key and value, one by one.
https://github.com/php/php-src/blob/master/ext/standard/array.c#L2439
/* {{{ proto array array_keys(array input [, mixed search_value[, bool strict]])
Return just the keys from the input array, optionally only for the specified search_value */
PHP_FUNCTION(array_keys)
{
zval *input, /* Input array */
*search_value = NULL, /* Value to search for */
**entry, /* An entry in the input array */
res, /* Result of comparison */
*new_val; /* New value */
int add_key; /* Flag to indicate whether a key should be added */
char *string_key; /* String key */
uint string_key_len;
ulong num_key; /* Numeric key */
zend_bool strict = 0; /* do strict comparison */
HashPosition pos;
int (*is_equal_func)(zval *, zval *, zval * TSRMLS_DC) = is_equal_function;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|zb", &input, &search_value, &strict) == FAILURE) {
return;
}
if (strict) {
is_equal_func = is_identical_function;
}
/* Initialize return array */
if (search_value != NULL) {
array_init(return_value);
} else {
array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(input)));
}
add_key = 1;
/* Go through input array and add keys to the return array */
zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(input), &pos);
while (zend_hash_get_current_data_ex(Z_ARRVAL_P(input), (void **)&entry, &pos) == SUCCESS) {
if (search_value != NULL) {
is_equal_func(&res, search_value, *entry TSRMLS_CC);
add_key = zval_is_true(&res);
}
if (add_key) {
MAKE_STD_ZVAL(new_val);
switch (zend_hash_get_current_key_ex(Z_ARRVAL_P(input), &string_key, &string_key_len, &num_key, 1, &pos)) {
case HASH_KEY_IS_STRING:
ZVAL_STRINGL(new_val, string_key, string_key_len - 1, 0);
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &new_val, sizeof(zval *), NULL);
break;
case HASH_KEY_IS_LONG:
Z_TYPE_P(new_val) = IS_LONG;
Z_LVAL_P(new_val) = num_key;
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &new_val, sizeof(zval *), NULL);
break;
}
}
zend_hash_move_forward_ex(Z_ARRVAL_P(input), &pos);
}
}
/* }}} */
Please see the array_keys definition, take note also for the comments which explain it pretty well:
Return just the keys from the input array, optionally only for the specified search_value
and later on:
Go through input array and add keys to the return array
The definition as follows for PHP 5.3 on ext/standard/array.c:
2427 /* {{{ proto array array_keys(array input [, mixed search_value[, bool strict]])
2428 Return just the keys from the input array, optionally only for the specified search_value */
2429 PHP_FUNCTION(array_keys)
2430 {
2431 zval *input, /* Input array */
2432 *search_value = NULL, /* Value to search for */
2433 **entry, /* An entry in the input array */
2434 res, /* Result of comparison */
2435 *new_val; /* New value */
2436 int add_key; /* Flag to indicate whether a key should be added */
2437 char *string_key; /* String key */
2438 uint string_key_len;
2439 ulong num_key; /* Numeric key */
2440 zend_bool strict = 0; /* do strict comparison */
2441 HashPosition pos;
2442 int (*is_equal_func)(zval *, zval *, zval * TSRMLS_DC) = is_equal_function;
2443
2444 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|zb", &input, &search_value, &strict) == FAILURE) {
2445 return;
2446 }
2447
2448 if (strict) {
2449 is_equal_func = is_identical_function;
2450 }
2451
2452 /* Initialize return array */
2453 if (search_value != NULL) {
2454 array_init(return_value);
2455 } else {
2456 array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(input)));
2457 }
2458 add_key = 1;
2459
2460 /* Go through input array and add keys to the return array */
2461 zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(input), &pos);
2462 while (zend_hash_get_current_data_ex(Z_ARRVAL_P(input), (void **)&entry, &pos) == SUCCESS) {
2463 if (search_value != NULL) {
2464 is_equal_func(&res, search_value, *entry TSRMLS_CC);
2465 add_key = zval_is_true(&res);
2466 }
2467
2468 if (add_key) {
2469 MAKE_STD_ZVAL(new_val);
2470
2471 switch (zend_hash_get_current_key_ex(Z_ARRVAL_P(input), &string_key, &string_key_len, &num_key, 1, &pos)) {
2472 case HASH_KEY_IS_STRING:
2473 ZVAL_STRINGL(new_val, string_key, string_key_len - 1, 0);
2474 zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &new_val, sizeof(zval *), NULL);
2475 break;
2476
2477 case HASH_KEY_IS_LONG:
2478 Z_TYPE_P(new_val) = IS_LONG;
2479 Z_LVAL_P(new_val) = num_key;
2480 zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &new_val, sizeof(zval *), NULL);
2481 break;
2482 }
2483 }
2484
2485 zend_hash_move_forward_ex(Z_ARRVAL_P(input), &pos);
2486 }
2487 }
2488 /* }}} */
The search that is performed is also explained on the PHP manual page for array_keys­Docs:
If the optional search_value is specified, then only the keys for that value are returned. Otherwise, all the keys from the input are returned.
See also the $strict parameter to influence how values are compared:
Determines if strict comparison (===) should be used during the search.
The two types of comparison == (Equal) and === (Identical) are documented as well.
I suspect this to be not so easy:
If you do NOT give the last parameter "strict" as true, PHP might
need to walk the array, along the way converting values to a
compareable type.
If you DO give "strict" as true, it might also be necessary to iterate the array to exclude wrong types

Convert Zval to char*

I want to convert Zval to char*. how do i do that in my php extension?
It the zval represents a string, you can use Z_STRVAL (or Z_STRVAL_P/Z_STRVAL_PP if you have a zval*/zval**).
Otherwise, you may have to convert the zval before :
zval *var;
char *cstr;
int cstrlen;
/* ... */
if (Z_TYPE_P(var) != IS_STRING) {
convert_to_string(var);
}
cstr = Z_STRVAL_P(var);
cstrlen = Z_STRLEN_P(var);
If you don't want to change the original zval and you want to change the resulting C string, you can do:
zval *var, *varcopy;
char *cstr;
int cstrlen;
if (Z_TYPE_P(var) != IS_STRING) {
ALLOC_INIT_ZVAL(varcopy);
*varcopy = *var;
INIT_PZVAL(varcopy); /* reset refcount and clear is_ref */
zval_copy_ctor(varcopy);
convert_to_string(varcopy);
} else {
varcopy = var;
}
cstrlen = Z_STRLEN_P(varcopy);
cstr = estrndup(Z_STRVAL_P(varcopy), cstrlen);
if (varcopy != var) {
zval_ptr_dtor(&varcopy);
}

list of registered shutdown functions

Is there any method to access to list of registered shutdown functions?
You can write an extension and look at BG(user_shutdown_function_names). Probably easier is to make a wrapper for register_shutdown_function that saves the shutdown functions to some array and call it instead.
(Untested)
#include "ext/standard/basic_functions.h"
//usual include suspects here
typedef struct _php_shutdown_function_entry {
zval **arguments;
int arg_count;
} php_shutdown_function_entry;
static void _shutdown_function_dtor(php_shutdown_function_entry *shutdown_function_entry) /* {{{ */
{
int i;
for (i = 0; i < shutdown_function_entry->arg_count; i++) {
zval_ptr_dtor(&shutdown_function_entry->arguments[i]);
}
efree(shutdown_function_entry->arguments);
}
static int _build_shutdown_array(php_shutdown_function_entry *entry, zval *arr TSRMLS_DC)
{
zval *inner;
zval *args;
int i;
array_init(inner);
array_init(args);
Z_ADDREF_P(entry->arguments[0]);
add_assoc_zval(inner, "callback", entry->arguments[0]);
for (i = 1; i < entry->arg_count; i++) {
Z_ADDREF_P(entry->arguments[i]);
add_next_index_zval(args, entry->arguments[i]);
}
add_assoc_zval(inner, "arguments", args);
add_next_index_zval(arr, inner);
}
PHP_FUNCTION(list_shutdown_functions)
{
if (zend_parse_parameters_none() == FAILURE)
return;
if (!BG(user_shutdown_function_names)) {
ALLOC_HASHTABLE(BG(user_shutdown_function_names));
zend_hash_init(BG(user_shutdown_function_names), 0, NULL,
(void (*)(void *)) _shutdown_function_dtor, 0);
}
array_init(return_value);
zend_hash_apply_with_argument(BG(user_shutdown_function_names),
(apply_func_arg_t) _build_shutdown_array, return_value TSRMLS_CC);
}
Other than keeping track yourself, no. The list of registered function names is not exposed to your PHP scripts. If you're open to extending PHP itself (this would be a simple task) then see Artefacto's answer.

Categories