4

Using PHP... an example. This produces a warning - as expected - and $myVar stays as bool(true).

$myVar = true;
$myVar[] = 'Hello';  // Warning: Cannot use a scalar value as an array

But this next example 'works', $myVar is converted into an array with a single element 'Hello'.

$myVar = false;
$myVar[] = 'Hello';  // Converted into an array

Results in:

array(1) {
  [0]=>
  string(5) "Hello"
}

Yet both bool(true) and bool(false) are both scalar. So why the difference? What rule in PHP governs this behaviour? Or is it 'just the way it is'?!

I initially thought it might be to do with type casting rules, but both bool(true) and bool(false) behave the same in this respect.

Thanks.

2
  • 1
    why on earth would you like to do such a thing anyways? :] Commented Jul 12, 2010 at 20:36
  • 1
    Well, this is very much a simplified example. A function is returning an array or (bool)false in an allowed error state. Elements are then added to this array using $myVar[$key]. In the case of (bool)false a new array is created - great. And then one day the function did something strange and returned int(1) and the script failed. Which then made me wonder why the script worked in the beginning with (bool)false! Commented Jul 12, 2010 at 21:09

4 Answers 4

7

So, even though I don't know why PHP does that, I looked at some Zend code and can at least tell you where you can find out how exactly PHP does it.

So, the important code is in zend_fetch_dimension_address.

So, let's cover the above cases:

If it IS_ARRAY - everything obvious.

If it IS_OBJECT throw error unless it has ArrayAccess.

If it IS_STRING throw an error, unless the strings length is zero.

If it IS_NULL create a new array.

If it IS_BOOL throw an error, unless it is false.

Otherwise, throw an error.

So, this confirms your and my tests:

Error if object, non-empty string, true and other scalars, i.e. long and double. No error if array, empty string, null and false. So basically it does an automatic cast on most (but not all) "falsy" values.

Sign up to request clarification or add additional context in comments.

7 Comments

Yeah, thanks for the breakdown. So it seems your initial response, "I think this is because the one is true, the other one is false :D That's PHP!", is pretty much on the button! :)
It's not really that odd at all. Converting an empty array to bool yields false while converting a non-empty array to bool yields true. It's a silly bit of syntactic sugar which allows you to test whether an array is empty via a simple if ($array). Going backwards, false should logically convert to an empty array. However, there is no sane way of converting true to a populated array, so an error is thrown.
@meagar: of course you can convert true to a populated array: php > var_dump((array)true); array(1) { [0]=> bool(true) }
@w3d: I completely forgot about this over time. But now looking at this again I think the behavior does make sense. It is a feature of PHP that arrays don't need to explicitly initialized, i.e. you can write $array[] = 'x' even if $array wasn't yet defined. The above behavior matches with this, by allowing to push to null values (in PHP null is approximately the same as undefined) and any falsey (null, false, '', array()) value in general.
@w3d: PS: In PHP 5.4 there was a related behavior change: Now you can't convert a falsey value to an object anymore (okay, you can, but you'll get a warning) by setting a property on it. (I.e. before 5.4 the behavior for arrays and objects was the same. Now objects don't allow that anymore.)
|
1

PHP is not a strongly typed language. You are assigning an array to a variable that contains a false value, not necessary a boolean false value.

Under the PHP covers, it must see $myVar as having an value that evaluates as empty and therefore allows the array assignment.

Again, if you look at PHP as a dynamic scripting language, this is not all that unexpected.

3 Comments

This can't be the whole truth. As I say in my answer, 0 even though empty throws an error, too.
Although PHP is not strongly typed, it is a boolean false value (=== false). Yeah as nikic states, it's not necessarily when the var evaluates to empty either.
@nikic, it's definitely not the whole true. But clearly it doesn't not do a strict equality check as mentioned by w3d. If you find out the whole truth, then you'll have answered your own question ;)
1

If you want to know why it is like this, then checkout php from here I think, compile it and do some step-by-step debugging with gdb... unless someone is good enough to find the piece of code responsible for this. Then look at the comments (if there are some) around the code responsible for this. As mentioned in the comments below, another way to find out would be to search the code for the error message. Let's do this!

    [greg@liche php-src-5.3]$ grep -rn --exclude-dir=".svn" "Cannot use a scalar value as an array" .
./tests/lang/bug29893.phpt:10:Warning: Cannot use a scalar value as an array in %sbug29893.php on line %d
./tests/lang/engine_assignExecutionOrder_002.phpt:12:// Warning: Cannot use a scalar value as an array in %s on line %d
./tests/lang/engine_assignExecutionOrder_002.phpt:94:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/zend_execute.c:1015:                             zend_error(E_WARNING, "Cannot use a scalar value as an array");
./Zend/tests/indexing_001.phpt:51:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:54:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:57:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:77:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:96:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:99:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:102:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:119:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:137:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:140:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:143:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:160:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:179:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:182:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:185:Warning: Cannot use a scalar value as an array in %s on line %d
./Zend/tests/indexing_001.phpt:202:Warning: Cannot use a scalar value as an array in %s on line %d

looks like it is in zend_execute.c, here is what I found :

 case IS_BOOL:
1223    if (type != BP_VAR_UNSET && Z_LVAL_P(container)==0) {
1224    goto convert_to_array;
1225    }
1226    /* break missing intentionally */
1227    
1228    default:
1229    if (type == BP_VAR_UNSET) {
1230    zend_error(E_WARNING, "Cannot unset offset in a non-array variable");
1231    AI_SET_PTR(result, &EG(uninitialized_zval));
1232    PZVAL_LOCK(&EG(uninitialized_zval));
1233    } else {
1234    zend_error(E_WARNING, "Cannot use a scalar value as an array");
1235    result->var.ptr_ptr = &EG(error_zval_ptr);
1236    PZVAL_LOCK(EG(error_zval_ptr));
1237    }
1238    break; 

I think the ZLVAL_P(container)==0 condition is responsible for this difference... lval means left value, the value that is being assigned... and I think it evaluates to 0.

8 Comments

I already am looking through Zend code but can't find the file which is responsible for this strangeness.
Found <svn.php.net/viewvc/php/php-src/trunk/Zend/…> line 612 convert_scalar_to_array. This may be the function, but it does not help me either. At least it doesn't explain anything to me.
If you typecast either (bool)true or (bool)false to an array you get essentially the same result... a single element array with either (bool)true or (bool)false as the elements value.
We don't know if there is an attempt to cast yet. The code we are looking for is the use of the operator '['. Searching for the error message in a checkouted local copy would be a good way to find it.
@greg0ire yes true. In fact the value is overwritten/replaced (or 'converted'), not cast at all.
|
0

Whilst PHP is loosely typed, types do still have some importance, hence === or !== for example.

The reason Nikic gets an error if it is reset to zero is it is actually now an integer, rather than a boolean.

To demonstrate, you could do the following and get the same error by casting.

$a = (int) false;
$a[] = 'goat'; #throws warning, thinks it's a 0

$a = (bool) 0;
$a[] = 'goat'; #works, thinks it's a false

I know thats just a pedantic example, however! (bool) true still evaluates to a 1.

3 Comments

Yes, int(0) - integer - scalar - fail. The only thought behind this initially was that perhaps it worked if the value 'evaluated' to false. But that's not the case. In terms of 'simple' types this array conversion only works with null, string('') and bool(false). The empty string and bool(false) appear to be special cases (as mentioned above).
I actually think it should work like isset does, anything in there barring a null will pass, or in the case here throw a warning!
Yes, that would certainly make sense. Although with PHP's ability to switch types to suit it appears to be trying to second guess the programmers true intentions? "Oh go on then, it's almost NULL, I'll let ya!"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.