PHP:为什么零长度字符串的数组语法将字符串转换为数组?

PHP: Why Does array Syntax on a Zero Length String Cast the String as an Array?

在PHP中,您可以使用数组语法访问字符串索引。下面的程序

<?php
$foo = "Hello";
echo $foo[0],"\n";
?>

回声

H

但是,如果您访问零长度字符串的第一个字符

<?php
$bar = "";
$bar[0] = "test";
var_dump($bar);
?>

PHP 将您的字符串转换为数组。上面的代码产生

array(1) {
    [0] =>
    string(4) "test"
}

即我的零长度字符串被转换为数组。类似的 "accessing an undefined index of a string" 示例不会产生这种转换行为。

$bar = " ";
$bar[1] = "test";
var_dump($bar);

生成字符串 t。即 $bar 仍然是一个字符串,不会转换为数组。

我知道当语言需要为您推断 and/or 自动转换变量时,这些不直观的边缘情况是不可避免的,但是有人知道这里的幕后发生了什么吗?

即在 PHP 中的 C/C++ 层级正在发生什么来实现这一点。为什么我的变量变成了数组。

PHP 5.6,如果重要的话。

我怀疑 "" 被视为未设置,然后被转换为数组。通常 "" != null != unset,但是,php 就此而言有点奇怪。

php > $a="test"; $a[0] = "yourmom"; var_dump( $a );
string(4) "yest"

php > $a=""; $a[0] = "yourmom"; var_dump( $a );
array(1) {
  [0]=>
  string(7) "yourmom"
}

php > var_dump((bool) "" == null);
bool(true)

php > var_dump((bool) $f == null);
PHP Notice:  Undefined variable: f in php shell code on line 1
PHP Stack trace:
PHP   1. {main}() php shell code:0

Notice: Undefined variable: f in php shell code on line 1

Call Stack:
  470.6157     225848   1. {main}() php shell code:0

bool(true)

当您对空字符串使用数组语法时,它被更改为数组的原因是因为索引 0 未定义,并且此时没有类型。这是一种案例研究。

<?php
$foo = "Hello"; // $foo[0] is a string "H"
echo $foo[0],"\n"; // H
$foo[0] = "same?"; // $foo[0] is still a string, "s" note that only the s is kept.
echo $foo,"\n"; // sello
echo $foo[0],"\n"; // s
$foo[1] = "b"; // $foo[1] is a string "b"
echo $foo,"\n"; // sbllo
$bar = ""; // nothing defined at position 0
$bar[0] = "t"; // array syntax creates an array with a string as the first index
var_dump($bar); // array(1) { [0] => string(1) "t" }

我试图在 PHP 来源中找到发生这种情况的位置。我在 PHP 内部和一般 C 方面的经验有限,所以如果我错了,请有人纠正我。

我认为这发生在 zend_fetch_dimension_address:

   if (EXPECTED(Z_TYPE_P(container) == IS_STRING)) {
        if (type != BP_VAR_UNSET && UNEXPECTED(Z_STRLEN_P(container) == 0)) {
            zval_ptr_dtor_nogc(container);
convert_to_array:
            ZVAL_NEW_ARR(container);
            zend_hash_init(Z_ARRVAL_P(container), 8, NULL, ZVAL_PTR_DTOR, 0);
            goto fetch_from_array;
        }

看起来如果容器是零长度字符串,它会先将其转换为数组,然后再对其执行任何操作。

在 C 级别上,当使用 [] 运算符完成赋值时,变量将转换为数组。当然,当它是一个字符串时,长度为 0 并且不是未设置类型的调用(例如 unset($test[0])).

case IS_STRING: {
                zval tmp;

                if (type != BP_VAR_UNSET && Z_STRLEN_P(container)==0) {
                    goto convert_to_array;
                }

https://github.com/php/php-src/blob/PHP-5.6.0/Zend/zend_execute.c#L1156

布尔假值发生相同的转换。

case IS_BOOL:
            if (type != BP_VAR_UNSET && Z_LVAL_P(container)==0) {
                goto convert_to_array;
            }

通过测试确认:

<?php
$bar = false;
$bar[0] = "test";
var_dump($bar);

输出:

array(1) { [0]=> string(4) "test" }

当使用 true 时:

<?php
$bar = true;
$bar[0] = "test";
var_dump($bar);

输出:

WARNING Cannot use a scalar value as an array on line number 3
bool(true)

https://github.com/php/php-src/blob/PHP-5.6.0/Zend/zend_execute.c#L1249

当值为 bool 类型且值为 true 时,将执行以下代码:

case IS_BOOL:
            if (type != BP_VAR_UNSET && Z_LVAL_P(container)==0) {
                goto convert_to_array;
            }
            /* break missing intentionally */

        default:
            if (type == BP_VAR_UNSET) {
                zend_error(E_WARNING, "Cannot unset offset in a non-array variable");
                result->var.ptr_ptr = &EG(uninitialized_zval_ptr);
                PZVAL_LOCK(EG(uninitialized_zval_ptr));
            } else { // Gets here when boolean value equals true.
                zend_error(E_WARNING, "Cannot use a scalar value as an array");
                result->var.ptr_ptr = &EG(error_zval_ptr);
                PZVAL_LOCK(EG(error_zval_ptr));
            }
            break;

PHP 5.6 版使用 ZEND 2.6.0 版