How can “[” be an operator in the PHP language specification?

前端 未结 3 580
萌比男神i
萌比男神i 2021-02-03 17:31

On the http://php.net/manual/en/language.operators.precedence.php webpage, the second highest precedence level contains a left-associative operator called [.

<
3条回答
  •  谎友^
    谎友^ (楼主)
    2021-02-03 18:30

    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".

提交回复
热议问题