Приведения типов и операции¶
Приведения типов¶
В многих ситуациях вы ожидаете получить zval определённого типа. В этом случае вы можете строго проверить нужный тип:
if (Z_TYPE_P(val) != IS_STRING) {
zend_type_error("Expected string");
return;
}
В качестве альтернативы вы можете выполнить приведение к нужному типу. Есть два способа выполнить приведение типов:
первый — это фактически изменить тип zval, используя одну из функций convert_to_*:
convert_to_string(val);
// Z_TYPE_P(val) == IS_STRING is guaranteed here.
Похожие функции существуют для всех остальных типов, для которых имеет смысл приведение типа:
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);
Кроме того, функция convert_scalar_to_number() может использоваться для преобразования zval либо в целое число,
либо в число с плавающей точкой, с той оговоркой, что массивы остаются массивами:
convert_scalar_to_number(val);
switch (Z_TYPE_P(val)) {
case IS_LONG:
php_printf("Long: " ZEND_LONG_FMT "\n", Z_LVAL_P(val));
break;
case IS_DOUBLE:
php_printf("Long: %H\n", Z_DVAL_P(val));
break;
case IS_ARRAY:
php_printf("Array\n");
break;
ZEND_EMPTY_SWITCH_DEFAULT_CASE()
}
Поскольку convert_to_* изменяет zval на месте, требуется осторожность для поддержания семантики
copy-on-write. Распространённая ошибка — писать код наподобие следующего:
zval *val;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), val) {
convert_to_string(val);
// Use val as string.
}
Здесь мы хотим пройти по массиву и обработать все его элементы как строки. Однако, поскольку convert_to_string()
работает на месте, это означает, что массив на самом деле изменяется. Таким образом, этот код допустим только если
вы единолично владеете массивом. В противном случае необходимо сначала выполнить отделение (separation):
zval *val;
SEPARATE_ARRAY(array);
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), val) {
convert_to_string(val);
// Use val as string.
}
Второй набор API для приведения типов избегает этой проблемы, возвращая преобразованное значение вместо изменения
типа самого zval. В случаях, когда его можно использовать, это обычно более удобно, более эффективно и более
безопасно (в отношении copy-on-write). При преобразовании в булевы значения, целые числа и числа с плавающей точкой
мы просто получаем результат типа bool, zend_long или double, и этого достаточно:
bool b = zend_is_true(val);
zend_long l = zval_get_long(val);
double d = zval_get_double(val);
Для строк мы получаем результат типа zend_string *, который мы должны освободить впоследствии. Если значение
уже является строкой, это просто увеличит счётчик ссылок. Если это не строка, функция либо вернёт существующую
интернированную строку, либо выделит новую:
zend_string *str = zval_get_string(val);
// Do something with str.
zend_string_release(str);
Для такого временного использования, когда мы не сохраняем долгосрочную ссылку на str, существует
дополнительный оптимизированный API:
zend_string *tmp_str;
zend_string *str = zval_get_tmp_string(val, &tmp_str);
// Do something with str.
zend_tmp_string_release(tmp_str);
Этот API работает так же, как zval_get_string(), но избегает увеличения и уменьшения счётчика ссылок в
обычном случае, когда значение уже является строкой.
Когда речь заходит о преобразованиях в строки в частности, есть ещё одна проблема, которую нужно учитывать:
методы __toString() могут выбрасывать исключения (на самом деле, преобразования в int и float тоже могут их
выбрасывать, но эта проблема обычно игнорируется). Это можно обработать, проверив EG(exception) после
преобразования в строку:
zend_string *str = zval_get_string(val);
if (EG(exception)) {
// zend_string_release(str) is safe, but not necessary here.
return;
}
zend_string_release(str);
Однако более идиоматичный и эффективный способ обработать эту ситуацию — использовать варианты try этих функций,
которые укажут, было ли выброшено исключение, через своё возвращаемое значение:
if (!try_convert_to_string(val)) {
// Exception thrown.
return;
}
zend_string *str = zval_try_get_string(val);
if (!str) {
// Exception thrown.
return;
}
zend_string_release(str);
zend_string *tmp_str;
zend_string *str = zend_try_get_tmp_string(val, &tmp_str);
if (!str) {
// Exception thrown.
return;
}
zend_tmp_string_release(tmp_str);
Операции¶
Пользовательские операции, такие как $op1 + $op2, реализованы внутри через соответствующие функции, такие как
add_function(), которые принимают выходной параметр-результат, за которым следуют входные операнды:
zval *op1 = /* ... */, *op2 = /* ... */;
zval result;
if (add_function(&result, op1, op2) == FAILURE) {
// Exception thrown.
return;
}
// Do something with result.
zval_ptr_dtor(&result);
Следует отметить, что эти функции на практике используются довольно редко, поскольку большая часть кода работает с zval конкретных типов, а не оперирует совершенно произвольными значениями. Полный набор функций таков:
zend_result add_function(zval *result, zval *op1, zval *op2); /* $result = $op1 + $op2 */
zend_result sub_function(zval *result, zval *op1, zval *op2); /* $result = $op1 - $op2 */
zend_result mul_function(zval *result, zval *op1, zval *op2); /* $result = $op1 * $op2 */
zend_result pow_function(zval *result, zval *op1, zval *op2); /* $result = $op1 ** $op2 */
zend_result div_function(zval *result, zval *op1, zval *op2); /* $result = $op1 / $op2 */
zend_result mod_function(zval *result, zval *op1, zval *op2); /* $result = $op1 % $op2 */
zend_result bitwise_or_function(zval *result, zval *op1, zval *op2); /* $result = $op1 | $op2 */
zend_result bitwise_and_function(zval *result, zval *op1, zval *op2); /* $result = $op1 & $op2 */
zend_result bitwise_xor_function(zval *result, zval *op1, zval *op2); /* $result = $op1 ^ $op2 */
zend_result boolean_xor_function(zval *result, zval *op1, zval *op2); /* $result = $op1 xor $op2 */
zend_result shift_left_function(zval *result, zval *op1, zval *op2); /* $result = $op1 << $op2 */
zend_result shift_right_function(zval *result, zval *op1, zval *op2); /* $result = $op1 >> $op2 */
zend_result concat_function(zval *result, zval *op1, zval *op2); /* $result = $op1 . $op2 */
zend_result bitwise_not_function(zval *result, zval *op1); /* $result = ~$op1 */
zend_result boolean_not_function(zval *result, zval *op1); /* $result = !$op1 */
zend_result increment_function(zval *op); /* ++$op */
zend_result decrement_function(zval *op); /* --$op */
zend_result compare_function(zval *result, zval *op1, zval *op2); /* $result = $op1 <=> $op2 */
zend_result is_equal_function(zval *result, zval *op1, zval *op2); /* $result = $op1 == $op2 */
zend_result is_not_equal_function(zval *result, zval *op1, zval *op2); /* $result = $op1 != $op2 */
zend_result is_identical_function(zval *result, zval *op1, zval *op2); /* $result = $op1 === $op2 */
zend_result is_not_identical_function(zval *result, zval *op1, zval *op2); /* $result = $op1 !== $op2 */
zend_result is_smaller_function(zval *result, zval *op1, zval *op2); /* $result = $op1 < $op2 */
zend_result is_smaller_or_equal_function(zval *result, zval *op1, zval *op2); /* $result = $op1 <= $op2 */
/* $op1 > $op2 is same as $op2 < $op1 */
/* $op1 >= $op2 is same as $op2 <= $op1 */
Для сравнений существуют ещё два варианта, которые возвращают результат сравнения вместо того, чтобы помещать его в zval:
bool zend_is_identical(zval *op1, zval *op2);
int zend_compare(zval *op1, zval *op2);
zend_compare() возвращает результат трёхстороннего сравнения, как оператор <=> в PHP, то есть меньше нуля,
равно нулю или больше нуля, в зависимости от того, меньше, равен или больше op1 по сравнению с op2.
Наконец, существует ряд вариантов с префиксом fast_. Это оптимизированные реализации, которые ограничивают
аргументы определёнными типами, либо инлайнят часть реализации и/или реализуют её с помощью встроенного
ассемблера:
/* op1 must have type IS_LONG, implementation uses inline assembly. */
static zend_always_inline void fast_long_increment_function(zval *op1);
static zend_always_inline void fast_long_decrement_function(zval *op1);
/* op1 and op2 must have type IS_LONG, implementation uses inline assembly. */
static zend_always_inline void fast_long_add_function(zval *result, zval *op1, zval *op2);
static zend_always_inline void fast_long_sub_function(zval *result, zval *op1, zval *op2);
/* op1, op2 may have any type, but IS_LONG and IS_DOUBLE addition is inlined. */
static zend_always_inline zend_result fast_add_function(zval *result, zval *op1, zval *op2);
/* op1, op2 may have any type, but IS_LONG, IS_DOUBLE and IS_STRING equality is inlined. */
static zend_always_inline bool fast_equal_check_function(zval *op1, zval *op2);
/* op1 must have type IS_LONG, op2 can have any type. */
static zend_always_inline bool fast_equal_check_long(zval *op1, zval *op2);
/* op1 must have type IS_DOUBLE, op2 can have any type. */
static zend_always_inline bool fast_equal_check_string(zval *op1, zval *op2);
/* op1, op2 may have any type, but part of the implementation is inlined. */
static zend_always_inline bool fast_is_identical_function(zval *op1, zval *op2);
static zend_always_inline bool fast_is_not_identical_function(zval *op1, zval *op2);