The object indirection operator (->) is missing from the PHP Operator Precedence table on the official documentation page. A comment on the bottom of the page says the precedence of such operator must be the highest. However, the following code shows that this is not true, and the comment is wrong:
class Foo {
public $i;
function __construct() { $this->i = 0; }
}
$foo = new Foo();
++$foo->i;
echo $foo->i;
OUTPUT:
1
So, what is the exact position of the object indirection operator (->) in the aforementioned PHP operator precedence table?
->operator precedence is higher than ++, but lower than new/clone .Through my test it's higher than [(array operator) in cli and lower than [ in Apatch httpd server, So I think this is a bug.
Related
I am a bit confused about the precedence of PHP's ?? operator.
https://www.php.net/manual/en/language.operators.precedence.php says that it has lower precedence than the / (division) operator. If that is true, $a??1/0 should always give an error, not only if $a is Null, as it first evaluates 1/0. As it doesn't give a "division by zero" error, I assume that the two sides of the ?? operator are evaluated as two different expressions and the second is only evaluated if the first yields Null. While it absolutely makes sense to implement it this way, doesn't this contradict the precedence stated on above page?
The null coalescing ?? operator will evaluate the right part of the expression only if the left part is Null.
This means it will evaluate the left part first in order to know if it is worth evaluating the right part.
See this as an optimisation of the interpreter to prevent executing some code if it does not reach it, and it doesn't look like an operator precedence issue.
Same in an if statement, when if (expression=true), the following elseif will not be evaluated.
Referring in the link you provided in the question, consider the following when you code:
Use of parentheses, even when not strictly necessary, can often
increase readability of the code by making grouping explicit rather
than relying on the implicit operator precedence and associativity.
The example you provide
<?php
//$a=12;
$a=Null;
echo $a??1/0;
will really be parsed as:
<?php
//$a=12;
$a=Null;
echo ($a) ?? (1/0) ;
The extra parentheses will prevent the next developer that works on your code from getting it wrong.
I'd like to have a reference stating clearly where PHP's arrow / method call operator (->) falls as regards the order of operator binding.
Unfortunately, the authoritative PHP manual page on operator precedence doesn't list it.
Example where someone could have doubts whether this could throw an exception because $price was first cast to string and only then the method call ->times() attempted:
return (string) $price->times($quantity);
In the answer, please indicate if there was any change here between PHP versions.
Bonus: does the static call (::) operator has the same precedence as ->?
$foo->bar(...) is classified in PHP's grammar as a member-call-expression.
This is a form of callable-variable, which itself is a form of variable. Essentially, a call to a member function on an object has the same precedence as any other "raw" variable - $foo->bar(...) and just $foo should be treated identically by the compiler. Neither is an "operator", and so they don't fit in the same hierarchy as the ones listed in the manual page you linked.
For your bonus question, :: is classed as a scoped-call-expression, which holds the same "precedence".
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]
In PHP you can initialize empty arrays with [] so in order to know how to define an array the precedence of the next character defines on how to initialize the array.
Since arrays are part of the syntax structure, it is done before any math, it simply has a higher precedence than other calculative operators for that reason.
var_dump([5, 6, 7] + [1, 2, 3, 4]); # 5674 - The array's must be known before applying the operator
However in all honesty I don't really understand the question. In most programming languages the [ and the ] are associated with arrays which is part of the base syntax that always have a high priority (if not the highest)
The result of:
var_dump(null != $a = 15);
var_dump($a);
is:
bool(true)
int(15)
Why is this script not triggering an error?
Since != (not equal operator) has a higher precedence than = (assignment operator), $a should be compared to null first?
The only reason I can find is that the documentation says that this is still legal: http://php.net/manual/en/language.operators.precedence.php#example-129
It seems to be an exception to what is shown in the table above.
This is not about operator precedence but about: operator precedence lists don't tell you the details (really, they never do), e.g. about the bison rules and the resulting pattern matching and stack reducing.
Let's take the statement null != $a = 15;, for simplicity without the var_dump.
This is how the parser "sees" this statement - or: sees that it is a statement.
(I hope this will be rendered with a fix-width font everywhere...)
null != $a = 15 ;
T_VARIABLE
identifier compound_variable T_LNUMBER
namespace_name reference_variable common_scalar
general_constant base_variable scalar
scalar base_variable_with_functions_calls expr_without_variable
expr_without_variable variable = expr
expr T_IS_NOT_EQUAL \______ expr_without_variable _________/
\__________________ expr ____________________________________________/ ;
\_________________ unticked_statement _______________________________________________/
statement
( You can look up the rules at https://github.com/php/php-src/blob/PHP-5.6.15/Zend/zend_language_parser.y )
There's no special rule for the assignment operator in this case; there simply isn't another way for the parser to match the statement, so precedence doesn't apply.
I need to assign one of two variables to a third variable, using the value of the second variable if the first is (bool)false or undefined.
I usually do this using ternary notation like so:
$foobar = ($some_prefix_and_some_variable_name) ? $some_prefix_and_some_variable_name : $bar ;
but sometimes this is not so pretty if the $foo variable name is very long, as it needs to be repeated in this notation.
My question is now, is it just as good to use this notation:
$foobar = $some_prefix_and_some_variable_name OR $foobar = $bar;
and is this notation interchangeable with the ternary version?
In PHP 5.3 there is also the short ternary notation:
$foobar = $fooooooooooooooooooooooooo ?: $bar ;
Because $foobar = $foo OR $foobar = $bar; evaluates to this:
Assign foo to foobar.
* Is there a value there?
* If not, assign bar to foobar.
While the other evaluates to:
Is there a value at foo?
* If so assign foobar = foo
* else assign foobar = bar
In the first example, if !foo you are assigning twice, if foo it can be faster. In the second example, you are only setting the value once total. I'll wager though that the speed difference is negligible.
The bigger issue here is readability. If 5.3 short notation isn't available, then I would still use the ternary notation if only because other programmers expect it.
For that matter, you will save more time and money by using the traditional ternary syntax if only because when people see your code they won't be asking themselves, WTF?
Since PHP 5.3 there is a special operator for this:
$foobar = $fooooooooooooooooooooooooo ?: $bar;
From the documentation on the ternary operator:
Since PHP 5.3, it is possible to leave out the middle part of the ternary operator. Expression expr1 ?: expr3 returns expr1 if expr1 evaluates to TRUE, and expr3 otherwise.
in matter of speed, this:
$foobar = $fooooooooooooooooooooooooo OR $foobar = $bar;
it's faster
No.
I don't know for sure whether the two notations are interchangeable, which means that you should not use the second one! Whether or not they are actually the same, if it is not immediately clear what the code does (as in this case), you should change it, unless you are absolutely sure that no one else will ever have to update this code. If I saw that written, I wouldn't be sure whether it was supposed to be like this, or whether there was some typo, and the bug was simply never discovered (seen it too many times!).
Ideally, with very long variable names, you should do this:
if($fooooooooooooooooooooooooo){
$foobar =$fooooooooooooooooooooooooo;
} else {
$foobar = $bar;
}
Just to make it easy to read, unless speed is of the essence, in which case you should use the ternary operator.