Search code examples
phptypescastingphp-internals

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


In PHP, you can use array syntax to access string indexes. The following program

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

echos out

H

However, if you access the first character of a zero length string

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

PHP turns your string into an array. The above code produces

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

i.e. my zero length string was cast to an array. Similar "accessing an undefined index of a string" examples don't produce this casting behavior.

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

Produces the string t. i.e. $bar remains a string, and is not converted into an array.

I get these sorts of unintuitive edge cases are inevitable when the language needs to infer and/or automatically cast variable for you, but does anyone know what's going on behind the scenes here?

i.e. What is happening at the C/C++ level in PHP to make this happen. Why does my variable get turned into an array.

PHP 5.6, if that matters.


Solution

  • On the C level the variable is converted to an array when assignment is done by the using [] operator. Of course when it is a string, has a length of 0 and is not a unset type of call( eg. 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

    Same conversion happens for boolean false values.

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

    Confirmed by using a test:

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

    Outputs:

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

    When using true:

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

    Outputs:

    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

    When the value is a type of bool and has a value of true the following code is executed:

    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 version 5.6 uses ZEND version 2.6.0