Reprinted please indicate the source http://blog.csdn.net/fanhengguang_php
PHP Kernel Principle Zvals Type Conversion and Operation
basic operation
Because zvals is a complex structure, you can't operate it directly, such as: zv1 + zv2. Even the simplest operations such as + are extremely complex for zvals, because you have to adapt to different types, such as: php allows you to add double types to string types (3.14 + 17'), and even allow you to add array types ([1, 2, 3] + [4, 5, 6]).
For this reason, php provides special functions to handle zvals operations, which are implemented by adding_function.
zval *a, *b, *result;
MAKE_STD_ZVAL(a);
MAKE_STD_ZVAL(b);
MAKE_STD_ZVAL(result);
ZVAL_DOUBLE(a, 3.14);
ZVAL_STRING(b, "17", 1);
/* result = a + b */
add_function(result, a, b TSRMLS_CC);
php_printf("%Z\n", result); /* 20.14 */
/* zvals a, b, result need to be dtored */
In addition to the add_function() addition operation function, there are a series of 2-variable operations (add and subtract two variables, etc.):
int add_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* + */
int sub_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* - */
int mul_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* * */
int div_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* / */
int mod_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* % */
int concat_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* . */
int bitwise_or_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* | */
int bitwise_and_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* & */
int bitwise_xor_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* ^ */
int shift_left_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* << */
int shift_right_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* >> */
int boolean_xor_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* xor */
int is_equal_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* == */
int is_not_equal_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* != */
int is_identical_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* === */
int is_not_identical_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* !== */
int is_smaller_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* < */
int is_smaller_or_equal_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); /* <= */
All these functions store the results of op1 and op2 operations in the result variable, which returns an int value for operation success or failure. Note that even if the operation fails, result is assigned a value such as false.
result zval needs to be allocated and initialized before the operation. The result and op1 can be the same, in which case a composite operation is performed:
zval *a, *b;
MAKE_STD_ZVAL(a);
MAKE_STD_ZVAL(b);
ZVAL_LONG(a, 42);
ZVAL_STRING(b, "3");
/* a += b */
add_function(a, a, b TSRMLS_CC);
php_printf("%Z\n", a); /* 45 */
/* zvals a, b need to be dtored */
Some binary operations are not in the list above, such as: > and >= There is no corresponding operation function. The reason is that this operation can be achieved by reversing is_smaller_function() and is_smaller_or_equal_function().
Another missing operation is & & and |, which is characterized by a short-circuit logic that cannot be implemented through a simple function.
In addition to binary operations, there are two binary operations:
int boolean_not_function(zval *result, zval *op1 TSRMLS_DC); /* ! */
int bitwise_not_function(zval *result, zval *op1 TSRMLS_DC); /* ~ */
These two operations are nothing special except that only one operand is received. Univariate operations + and - have no corresponding processing functions, because they can be implemented by 0 + $value and 0 - $value, and by adding_function() and sub_function.
Finally, two operations + + and --:
int increment_function(zval *op1); /* ++ */
int decrement_function(zval *op1); /* -- */
Instead of a result variable, these two functions directly modify the operands passed to them.
compare
The comparison functions described above all perform deterministic operations. For example, is_equal_function() corresponds to ==, is_smaller_function() corresponds to <, and compare_function() when comparing another operation function, it can calculate a more general resu lt:
zval *a, *b, *result;
MAKE_STD_ZVAL(a);
MAKE_STD_ZVAL(b);
MAKE_STD_ZVAL(result);
ZVAL_LONG(a, 42);
ZVAL_STRING(b, "24");
compare_function(result, a, b TSRMLS_CC);
if (Z_LVAL_P(result) < 0) {
php_printf("a is smaller than b\n");
} else if (Z_LVAL_P(result) > 0) {
php_printf("a is greater than b\n");
} else /*if (Z_LVAL_P(result) == 0)*/ {
php_printf("a is equal to b\n");
}
/* zvals a, b, result need to be dtored */
For less than or equal to, compare_function() sets the result to - 1, 1, 0. There are also a number of similar comparison functions:
int compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC);
int numeric_compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC);
int string_compare_function_ex(zval *result, zval *op1, zval *op2, zend_bool case_insensitive TSRMLS_DC);
int string_compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC);
int string_case_compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC);
#ifdef HAVE_STRCOLL
int string_locale_compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC);
#endif
Again, again, all of these accept two operators and one result zval and return success or failure.
Compare_function() is the comparison in ordinary PHP (such as operator <,>, = behavior always). numeric_compare_function() converts operands to double type for comparison
string_compare_function_ex() is used to compare strings. It accepts an additional parameter to indicate whether the string is case sensitive. You can also use string_compare_function() (case sensitive) and string_case_compare_function() (insensitive) directly instead.
Type conversion
Because php variable types are dynamic. So when writing extensions, the functions you provide need to be able to handle various types of variables.
The solution is that you can convert any type of zval to a specific type. php provides a series of functions to provide this functionality:
void convert_to_null(zval *op);
void convert_to_boolean(zval *op);
void convert_to_long(zval *op);
void convert_to_double(zval *op);
void convert_to_string(zval *op);
void convert_to_array(zval *op);
void convert_to_object(zval *op);
void convert_to_long_base(zval *op, int base);
void convert_to_cstring(zval *op);
Conv_to_long_base provides multi-base support relative to convert_to_long (e.g. hexadecimal). Conv_to_cstring versus convert_to_cstring uses region-independent rendering when doubling strings, such as using. as a delimiter instead of'3,14'.
Conv_to_* functions directly modify the zval passed to them:
zval *zv_ptr;
MAKE_STD_ZVAL(zv_ptr);
ZVAL_STRING(zv_ptr, "123 foobar", 1);
convert_to_long(zv_ptr);
php_printf("%ld\n", Z_LVAL_P(zv_ptr));
zval_dtor(&zv_ptr);
If a zval is used in more than one place (refcount > 1), typing it directly will cause errors.
To solve this problem, php provides other functions convert_to_*_ex:
void convert_to_null_ex(zval **ppzv);
void convert_to_boolean_ex(zval **ppzv);
void convert_to_long_ex(zval **ppzv);
void convert_to_double_ex(zval **ppzv);
void convert_to_string_ex(zval **ppzv);
void convert_to_array_ex(zval **ppzv);
void convert_to_object_ex(zval **ppzv);
These functions take a zval** parameter and execute SEPARATE_ZVAL_IF_NOT_REF():
#define convert_to_ex_master(ppzv, lower_type, upper_type) \
if (Z_TYPE_PP(ppzv)!=IS_##upper_type) { \
SEPARATE_ZVAL_IF_NOT_REF(ppzv); \
convert_to_##lower_type(*ppzv); \
}
In addition, this kind of function is consistent with convert_to_*.
zval **zv_ptr_ptr = /* get function argument */;
convert_to_long_ex(zv_ptr_ptr);
php_printf("%ld\n", Z_LVAL_PP(zv_ptr_ptr));
/* No need to dtor as function arguments are dtored automatically */
But that's not enough. Let's consider a similar situation. However, value is read from array:
zval *array_zv = /* get array from somewhere */;
/* Fetch array index 42 into zv_dest (how this works is not relevant here) */
zval **zv_dest;
if (zend_hash_index_find(Z_ARRVAL_P(array_zv), 42, (void **) &zv_dest) == FAILURE) {
/* Error: Index not found */
return;
}
convert_to_long_ex(zv_dest);
php_printf("%ld\n", Z_LVAL_PP(zv_dest));
/* No need to dtor because array values are dtored automatically */
Using convert_to_long_ex() in the above example prevents modifying the zval reference outside the array, but it still modifies the value of zval in the array. This is true for some scenarios, but generally we are not like modifying the array itself.
In this case, we can only make zval copies before type conversion:
zval **zv_dest = /* get array value */;
zval tmp_zv;
ZVAL_COPY_VALUE(&tmp_zv, *zv_dest);
zval_copy_ctor(&tmp_zv);
convert_to_long(&tmp_zv);
php_printf("%ld\n", Z_LVAL(tmp_zv));
zval_dtor(&tmp_zv);
The last call to zval_dtor (& tmp_zv) in the example above is not necessary, because you know that this zval is an IS_LONG type, which does not need to be destroyed. But for other types of zval, variable destruction is needed.
If conversion to long or double type is very common to you, you can write them as a help function, which does not need to modify any zval, a simple implementation of long conversion:
long zval_get_long(zval *zv) {
switch (Z_TYPE_P(zv)) {
case IS_NULL:
return 0;
case IS_BOOL:
case IS_LONG:
case IS_RESOURCE:
return Z_LVAL_P(zv);
case IS_DOUBLE:
return zend_dval_to_lval(Z_DVAL_P(zv));
case IS_STRING:
return strtol(Z_STRVAL_P(zv), NULL, 10);
case IS_ARRAY:
return zend_hash_num_elements(Z_ARRVAL_P(zv)) ? 1 : 0;
case IS_OBJECT: {
zval tmp_zv;
ZVAL_COPY_VALUE(&tmp_zv, zv);
zval_copy_ctor(&tmp);
convert_to_long_base(&tmp, 10);
return Z_LVAL_P(tmp_zv);
}
}
}
In the example above, the value of type conversion is returned directly without any copy of zval (except for object type, copy is inevitable). Using the above function type conversion becomes very simple:
zval **zv_dest = /* get array value */;
long lval = zval_get_long(*zv_dest);
php_printf("%ld\n", lval);
The php standard library contains a similar conversion function zend_is_true().
zval *zv_ptr;
MAKE_STD_ZVAL(zv_ptr);
ZVAL_STRING(zv, "", 1);
php_printf("%d\n", zend_is_true(zv)); // 0
zval_dtor(zv);
ZVAL_STRING(zv, "foobar", 1);
php_printf("%d\n", zend_is_true(zv)); // 1
zval_ptr_dtor(&zv);
zend_make_printable_zval() is another function that avoids unnecessary copies in type conversion. This function is similar to the string conversion function convert_to_string(), but uses different api s. Typical applications are as follows:
zval *zv_ptr = /* get zval from somewhere */;
zval tmp_zval;
int tmp_zval_used;
zend_make_printable_zval(zv_ptr, &tmp_zval, &tmp_zval_used);
if (tmp_zval_used) {
zv_ptr = &tmp_zval;
}
PHPWRITE(Z_STRVAL_P(zv_ptr), Z_STRLEN_P(zv_ptr));
if (tmp_zval_used) {
zval_dtor(&tmp_zval);
}
The second parameter of the function is a temporary zval, and the third parameter is an int pointer. When the function uses temporary zval, it will be set to 1, otherwise it will be set to 0.
With tmp_zval_use, you can decide whether to use the original zval or the temporary zval. Usually, the temporary zval will be simply copied to the original zval zv_ptr= & & tmp_zval. So you can always use zv_ptr instead of always judging which zval to use by conditions.
Finally, you need to destroy the temporary variable zval_dtor (& tmp_zval) unless you really use it.
Another function associated with type conversion is_numeric_string(). This function checks whether the string is numeric and converts its value to long or double.
long lval;
double dval;
switch (is_numeric_string(Z_STRVAL_P(zv_ptr), Z_STRLEN_P(zv_ptr), &lval, &dval, 0)) {
case IS_LONG:
/* String is an integer those value was put into `lval` */
break;
case IS_DOUBLE:
/* String is a double those value was put into `dval` */
break;
default:
/* String is not numeric */
}
The last parameter indicates whether errors are allowed, and if set to 0, this type of 123abc will not be converted. If set to 1, 123 ABC is converted to 123. If set to - 1, parameters like 123 ABC can be passed, but a notice alarm will be triggered.
This function also accepts 16 prohibited numbers such as 0xabc. Unlike convert_to_long() and convert_to_double(), functions in convert form convert "0xabc" to 0.
In addition, a function convert_scalar_to_number is used as follows:
zval *zv_ptr;
MAKE_STD_ZVAL(zv_ptr);
ZVAL_STRING(zv_ptr, "3.141", 1);
convert_scalar_to_number(zv_ptr);
switch (Z_TYPE_P(zv_ptr)) {
case IS_LONG:
php_printf("Long: %ld\n", Z_LVAL_P(zv_ptr));
break;
case IS_DOUBLE:
php_printf("Double: %G\n", Z_DVAL_P(zv_ptr));
break;
case IS_ARRAY:
/* Likely throw an error here */
break;
}
zval_ptr_dtor(&zv_ptr);
/* Double: 3.141 */
Similarly, there is a convert_scalar_to_number_ex function that accepts a zval** variable and separates zval before conversion.