How can "[" be an operator in the PHP language specification?
On the http://php.net/manual/en/language.operators.precedence.php webpage, the second highest precedence level contains a left-associative operator called [
.
I don't understand that. Is it the [
used to access/modify array entries, as in $myArray[23]
? I cannot imagine any code snippet where we would need to know the "precedence" of it wrt other operators, or where the "associativity" of [
would be useful.
This is a very valid question.
1. Precedence in between [...]
First there is never an ambiguity to what PHP should evaluate first when looking at the
right side of the [
, since the bracket requires a closing one to go with it, and so
every operator in between has precedence over the opening bracket.
Example:
$a[1+2]
The +
has precedence, i.e. first 1+2 has to be evaluated before PHP can determine which
element to retrieve from $a.
But the operator precedence list is not about this.
2. Associativity
Secondly there is an order of evaluating consecutive pairs of []
, like here:
$b[1][2]
PHP will first evaluate $b[1]
and then apply [2]
to that. This is left-to-right
evaluation and is what is intended with left associativity.
But the question at hand is not so much about associativity, but about precedence with regards to other operators.
3. Precedence over operators on the left side
The list states that clone
and new
operators have precedence over [
, and this is not easy to test.
First of all, most of the constructs where you would combine new
with square brackets are considered invalid
syntax. For example, both of these statements:
$a = new myClass()[0];
$a = new myClass[0];
will give a parsing error:
syntax error, unexpected '['
PHP requires you to add parentheses to make the syntax valid. So there is no way we can test the precedence rules like this.
But there is another way, by using a variable containing a class name:
$a = new $test[0];
This is valid syntax, but now the challenge is to make a class that creates something that acts like an array.
This is not trivial to do, as an object property is referenced like this: obj->prop
, not
like obj["prop"]
. One can however use the ArrayObject class which can deal with square brackets. The idea is to extend this class and redefine the offsetGet method to make sure a freshly made object of that class has array elements to return.
To make objects printable, I ended up using the magical method __toString, which is executed when an object needs to be cast to a string.
So I came up with this set-up, defining two similar classes:
class T extends ArrayObject {
public function __toString() {
return "I am a T object";
}
public function offsetGet ($offset) {
return "I am a T object's array element";
}
}
class TestClass extends ArrayObject {
public function __toString() {
return "I am a TestClass object";
}
public function offsetGet ($offset) {
return "I am a TestClass object's array element";
}
}
$test = "TestClass";
With this set-up we can test a few things.
Test 1
echo new $test;
This statement creates a new TestClass instance, which then needs to be converted to string, so the __toString method is called on that new instance, which returns:
I am a TestClass object
This is as expected.
Test 2
echo (new $test)[0];
Here we start with the same actions, as the parentheses force the new
operation to be executed first. This time PHP does not convert the created object to string, but requests array element 0 from it. This request is answered by the offsetGet method, and so the above statement outputs:
I am a TestClass object's array element
Test 3
echo new ($test[0]);
The idea is to force the opposite order of execution. Sadly enough, PHP does not allow this syntax, so will have to break the statement into two in order to get the intended evaluation order:
$name = $test[0];
echo new $name;
So now the [
is executed first, taking the first character of the value of
$test, i.e. "T", and then new
is applied to that. That's why I
defined also a T class. The echo
calls __toString on that instance, which yields:
I am a T object
Now comes the final test to see which is the order when no parentheses are present:
Test 4
echo new $test[0];
This is valid syntax, and...
4. Conclusion
The output is:
I am a T object
So in fact, PHP applied the [
before the new
operator, despite what is stated in the
operator precedence table!
5. Comparing clone
with new
The clone
operator has similar behaviour in combination with [
. Strangely enough, clone
and new
are not completely equal in terms of syntax rules. Repeating test 2 with clone
:
echo (clone $test)[0];
yields a parsing error:
syntax error, unexpected '['
But test 4 repeated with clone
shows that [
has precedence over it.
@bishop informed that this reproduces the long standing documentation bug #61513: "clone
operator precedence is wrong".
It just means the array variable (left associativity - $first) will be evaluated before the array key (right associativity - $second)
$first[$second]
This have lot of sense when array has multiple dimensions
$first[$second][$third][$fourth]