tags:

views:

99

answers:

5

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.

A: 

I think this is because the one is true, the other one is false :D That's PHP!

You could try the same with null and '' which would both result in no error. Using 'a' instead would throw an error (though a different one).

I think PHP here treates values evaluating to false as somehow undefined.

Though I wonder about one thing: If I use 0 again I get an error message. This is strange, because 0 obviously evaluates to false, too.

nikic
+1  A: 

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.

Jason McCreary
This can't be the whole truth. As I say in my answer, `0` even though `empty` throws an error, too.
nikic
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.
w3d
@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 ;)
Jason McCreary
+1  A: 

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.

greg0ire
I already am looking through Zend code but can't find the file which is responsible for this strangeness.
nikic
Found <http://svn.php.net/viewvc/php/php-src/trunk/Zend/zend_operators.c?view=markup> 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.
nikic
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.
w3d
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
@greg0ire yes true. In fact the value is overwritten/replaced (or 'converted'), not cast at all.
w3d
Thanks for pasting the code snippet. From seeing the original code (nikic's link above) I had wondered whether `Z_LVAL_P(container)==0` was the `if it's false` check.
w3d
Yes, I think so. I don't want to go looking for the definition of this macro, but thinking logically: It probably gets a pointer (`P`) to the `lval` (`long`) part of the `zvalue` of a `zval`. This is probably because PHP saves a boolean as if it were an integer, but set's the `type` as boolean. And then PHP compares to `0`. This is strange to me, because the return value of `Z_LVAL_P` should be a pointer, so PHP checks for a `NULL` pointer, but this is probably only because I lack some knowledge on pointer arithmetic ^^.
nikic
I am not sure whether P stands for pointer, since PTR is used a few lines further, and I think the L stands for left, look here : http://en.wikipedia.org/wiki/L-value
greg0ire
+5  A: 

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 (http://svn.php.net/viewvc/php/php-src/trunk/Zend/zend_execute.c?view=markup on line 1087).

So, let's cover the above cases:

If it IS_ARRAY - everything obvious.

If it IS_OBJECT throw error, unless - now, I didn't understood this part but I think - it has ArrayAccess.

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

If it IS_NULL new array.

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

Otherwise, throw an error.

(I highlighted the more important parts.)

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.

As I think this behavior is very odd I will ask on some PHP mailing list, whether this is expected.

nikic
+1, good explanation
greg0ire
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! :)
w3d
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
@meagar: of course you can convert true to a populated array: php > var_dump((array)true); array(1) { [0]=> bool(true) }
greg0ire
@meagar: I would think this was logic too but only if `0` also lead to an empty array. That way all values evaluating to `false` if you `(bool)` cast them, would become an empty array. But as it right now...
nikic
Be sure to post back if you hear anything on your PHP mailing list. :) It is odd, but there is some logic behind it and if they were to _change_ this then I bet there would be some broken code! I've changed my script to explicitly check for this now. Thanks.
w3d
A: 

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.

corrodedmonkee
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).
w3d
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!
corrodedmonkee
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!"
w3d