The rules PHP uses for type juggling with operators are complex, varying by operator as well as the types and values of the operands.
This can lead to surprising results, where a logical statement can have an illogical result. See the “Motivating examples” section below for details.
This RFC proposes a new directive strict_operators
, which limits the type juggling done by operators to avoid unexpected results.
Add a new declare()
directive strict_operators
, which accepts either 0
or 1
as the value to indicate that the strict_operators mode should be disabled or enabled.
The default value for this directive is 0, i.e. strict_operators not enabled and there will be no change from the current behavior of PHP i.e. by default, PHP files do not use the new strict_operators rules.
If strict_operators is enabled, the following stricter rules will be used;
TypeError
for unsupported typesIn case an operator can work with several (or all) types, the operand types need to match as no type conversion will be done by those operators.
The one exception is that widening primitive conversion is allowed for int
to float
. When doing a operation with an int
and a float
, the int
will silently casted to a float
.
Using strict_operators
never changes the outcome of an operation.
This section details which types operands support, and what type juggling will be supported (if any) for them.
Arithmetic operators +
, -
, *
, /
, %
, and **
will only support int
or float
operands. Attempting to use an unsupported operand will throw a TypeError
.
The +
operator will still be available for arrays as union operator, requiring both values to be arrays.
The incrementing/decrementing operators ++
and --
will only support int
or float
operands. Attempting to use an unsupported operand will throw a TypeError
.
The bitwise operators &
, |
, ^
, and ~
will support int
and string
operands . The type of both operands need to match. If the operands are of different types or when using an unsupported operand, a TypeError
will be thrown.
The bitwise shift operators >>
and <<
will only support int
operands. Attempting to use an unsupported operand will throw a TypeError
.
Enabling the strict_operators directive will not affect the identical (===
) and not identical (!==
) operators.
All other comparison operators (==
, !=
, <
, >
, <=
, >=
, <=>
) will only support int
or float
operands.
When used with a bool
, string
, array
, object
, or resource
operand, a TypeError
will be thrown.
Custom compare handlers of objects (like DateTime
and gmp
) will be applied regardless of strict_operators
. The ZEND_COMPARE_OBJECTS_FALLBACK
macro will throw a TypeError
if two objects are compared with different handlers.
The concatenation operator .
will only support concatenating null
, int
, float
, string
, and stringable object operands.
If any of the operands is a bool
, array
, resource
, or non-stringable object, a TypeError
will be thrown.
When a string is specified in double quotes or with heredoc, variables are parsed within it. The string interpolation is performed with the same rules as for string concatenation.
i.e. this code
echo "He drank some $juice juice.";
has the same rules as using the string concatenation operator.
echo "He drank some " . $juice . " juice.";
There a no changes to the logical operators &&
, ||
, !
, and
, or
, and xor
.
There a no changes to the ternary (?:
) and null coalescing (??
) operator.
The following section demonstrates code that is written logical, but has illogical results.
The meaning of comparison operators currently change based on the type of each operand. Strings are compared as byte sequence. If one of the operands is an integer the operator performs a numeric comparison.
This allows for statements that defy mathematical logic and may be experienced as unexpected behavior.
$a = '42'; $b = 10; $c = '9 eur'; if (($a > $b) && ($b > $c) && ($c > $a)) { // Unexpected }
Non-strict comparison uses a “smart” comparison method that treats strings as numbers if they are numeric. The meaning of the operator changes based on the value of both operands.
This can lead to issues when numeric comparison is not expected, for example between two hexidecimal values. The hexidecimal value is instead interpreted as number with scientific notation.
$red = '990000'; $purple = '9900e2'; $red == $purple; // true
It may also cause issues with sorting, as the meaning of the comparison operators differers based on the operands (similar to mixed type comparison).
function sorted(array $arr) { usort($arr, function($x, $y) { return $x <=> $y; }); return $arr; } sorted(['100', '5 eur', '62']); // ['100', '5 eur', '62'] sorted(['100', '62', '5 eur']); // ['5 eur', '62', '100'] sorted(['62', '100', '5 eur']); // ['62', '100', '5 eur']
Using the >
, >=
, <
, <=
and <=>
operators on arrays or objects that don't have the same keys in the same order gives unexpected results.
In the following example $a
is both greater than and less than $b
$a = ['x' => 1, 'y' => 22]; $b = ['y' => 10, 'x' => 15]; $a > $b; // true $a < $b; // true
Strict comparison requires that arrays have keys occurring in the same order, while non-strict comparison allows out-of-order keys.
['a' => 'foo', 'b' => 'bar'] == ['b' => 'bar', 'a' => 0]; // true
To compare the values of two arrays in a strict way while not concerned about the order, requires ordering the array by key prior to comparison.
Operators can do any of the following for unsupported operands
Please take a look at this list of all combinations of operators and operands.
None known. As this RFC proposes a new directive, it should only affect code is new or updated to use the strict_operators directive.
This is proposed for PHP 8.0.
This RFC
This RFC has an FAQ that answers some questions
Primary vote: Accept the RFC and merge the patch? Yes/No. Requires a 2/3 majority.