rfc:strict_operators
Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision Next revision Both sides next revision | ||
rfc:strict_operators [2019/07/09 13:09] nikic Formatting fix |
rfc:strict_operators [2020/07/06 19:03] jasny |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Strict operators directive ====== | ====== PHP RFC: Strict operators directive ====== | ||
- | | + | |
- | * Date: 2019-05-25 | + | |
- | * Author: Arnold Daniels, jasny@php.net | + | * Date: 2020-07-06 (first version: 2019-05-25) |
+ | * Author: Arnold Daniels, | ||
* Status: Under Discussion | * Status: Under Discussion | ||
* First Published at: http:// | * First Published at: http:// | ||
- | |||
===== Introduction ===== | ===== Introduction ===== | ||
- | PHP performs implicit type conversion for most operators. The rules of conversion are complex, depending on the operator as well as on the type and value of the operands. This can lead to surprising results, where a statement seemingly contradicts itself. This RFC proposes a new directive '' | ||
- | Making significant changes to the behavior of operators | + | The rules PHP uses for type juggling with operators |
- | ===== Motivating examples | + | This can lead to surprising results, where a logical statement can have an illogical result. See the "Motivating examples" section below for details. |
- | ==== Mixed type comparison ==== | + | This RFC proposes |
- | Mathematics states that "if '' | + | |
- | <code php> | + | ===== Proposal ===== |
- | if (($a > $b) && ($b > $c)) { | + | |
- | assert($a > $c); | + | |
- | } | + | |
- | </ | + | |
- | This assertion fails when choosing values of different types | + | Add a new '' |
- | <code php> | + | 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. |
- | $a = ' | + | |
- | $b = 10; | + | |
- | $c = '9 eur'; | + | |
- | if (($a > $b) && ($b > $c)) { | + | If strict_operators is enabled, the following stricter rules will be used; |
- | assert($a > $c); | + | |
- | } | + | |
- | </ | + | |
- | ==== Numeric string comparison ==== | + | * Operators may perform type conversion, but not type juggling: |
- | Non-strict comparison uses a " | + | * Type conversion is not based on the type of any of the operands |
+ | * Type conversion is not based on the value of any of the operands | ||
+ | * Operators will throw a '' | ||
- | Using the '' | + | In case an operator |
- | <code php> | + | The one exception is that [[http:// |
- | function sorted(array $arr) { | + | |
- | usort($arr, function($x, | + | |
- | return $arr; | + | |
- | } | + | |
- | sorted(['100', '5 eur', ' | + | Using '' |
- | sorted([' | + | |
- | sorted([' | + | |
- | </ | + | |
- | ==== Array comparison | + | ===== Details for operands |
- | Using the ''>'', | + | |
- | In the following example '' | + | This section details which types operands support, |
- | <code php> | + | ==== Arithmetic operators ==== |
- | $a = [' | + | |
- | $b = [' | + | |
- | $a > $b; // true | + | Arithmetic operators '' |
- | $a < $b; // true | + | |
- | </ | + | |
- | The logic of relational operators other than '' | + | The '' |
- | <code php> | + | ==== Incrementing/Decrementing operators ==== |
- | [1] < [50]; // true | + | |
- | [1, 1] < [50]; // false | + | |
- | </ | + | |
- | This is not a proper method to compare the size of the array, as two operands | + | The incrementing/ |
- | If two arrays have the same number of items but not the same keys, the ''<'', | + | ==== Bitwise Operators ==== |
- | <code php> | + | The bitwise operators |
- | [1] < ['bar' | + | |
- | [1] > ['bar' | + | |
- | </ | + | |
- | In case the two arrays have the same number of items and the same keys but in a different order, an element by element comparison is done. The ''>'' | + | The bitwise shift operators |
- | <code php> | + | ==== Comparison operators ==== |
- | $a = [' | + | |
- | $b = [' | + | |
- | $a > $b; // true | + | Enabling the strict_operators directive will not affect the identical ('' |
- | $a < $b; // true | + | |
- | </ | + | |
- | In the statement with the '' | + | All other comparison operators ('' |
- | ==== Strict vs non-strict comparison of arrays ==== | + | When used with a '' |
- | Strict comparison requires that arrays have keys occurring in the same order, while non-strict comparison allows out-of-order keys. | + | |
- | <code php> | + | Custom compare handlers of objects (like '' |
- | ['a' | + | |
- | </ | + | |
- | To compare the values of two arrays in a strict way, while not concerned about the order requires ordering the array by key. | + | ==== String concatenation ==== |
- | ==== Type juggling of arithmetic operators ==== | + | The concatenation operator '' |
- | The behavior of arithmetic operators for non-scalar types is inconsistent. | + | |
- | Most arithmetic operations throw an '' | + | If any of the operands is a '' |
- | Objects and resources are always cast to integers or floats. In case of an object, this results in a notice. For resources, this will succeed silently using the resource id as a number. | + | === String interpolation === |
- | | ^ '' | + | 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. |
- | ^ array | error | error | - | | + | |
- | ^ object | + | |
- | ^ resource | + | |
- | ==== Numeric strings and bitwise operators ==== | + | i.e. this code |
- | Bitwise operators have an alternative operation if both operands are strings. This is regardless of the value of the strings. | + | |
- | + | ||
- | > If both operands for the &, | and ^ operators are strings, then the operation will be performed on the ASCII values of the characters that make up the strings and the result will be a string. In all other cases, both operands will be converted to integers and the result will be an integer. | + | |
- | + | ||
- | Bitwise operators are therefore the only operators that don't treat numeric strings as numbers. | + | |
<code php> | <code php> | ||
- | "22" | + | echo "He drank some $juice juice."; |
- | 22 & 12; // 4 | + | |
</ | </ | ||
- | ==== Switch control structure ==== | + | has the same rules as using the string concatenation operator. |
- | The '' | + | |
<code php> | <code php> | ||
- | function match($value) | + | echo "He drank some " |
- | { | + | |
- | switch ($value) { | + | |
- | case 2: | + | |
- | return | + | |
- | break; | + | |
- | case 1: | + | |
- | return | + | |
- | break; | + | |
- | case 0: | + | |
- | return " | + | |
- | break; | + | |
- | default: | + | |
- | throw new Exception(" | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | match(" | + | |
</ | </ | ||
- | In case both the expression and the condition operands are both numeric strings, both are converted to an integer. This can be unexpected; | + | ==== Logical Operators ==== |
- | <code php> | + | There a no changes to the logical operators '' |
- | function match($value) | + | |
- | { | + | |
- | switch ($value) { | + | |
- | case " | + | |
- | return " | + | |
- | break; | + | |
- | case " | + | |
- | return " | + | |
- | break; | + | |
- | default: | + | |
- | throw new Exception(" | + | |
- | } | + | |
- | } | + | |
- | match(" | + | ==== Ternary |
- | </ | + | |
+ | There a no changes to the ternary ('' | ||
- | ==== All combinations | + | ===== Motivating examples ===== |
- | Operators can do any of the following | + | The following |
- | * Cast | + | ==== Mixed type comparison ==== |
- | * silent | + | |
- | * with notice | + | |
- | * with warning | + | |
- | * causing a catchable error (fatal) | + | |
- | * Notice + cast | + | |
- | * Warning + cast | + | |
- | * Throw Error | + | |
- | * No operation | + | |
- | Please take a look at this [[https:// | + | 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 |
- | ===== Proposal ===== | + | This allows |
- | By default, all PHP files are in weak type-checking mode for operators. A new '' | + | |
- | + | ||
- | In strict type-checking mode, operators | + | |
- | + | ||
- | * Typecasting is not based on the type of the other operand | + | |
- | * Typecasting is not based on the value of any of the operands | + | |
- | * Operators will throw a '' | + | |
- | + | ||
- | In case an operator can work with several (or all) types, the operands need to match as no casting will be done by those operators. | + | |
- | + | ||
- | The one exception is that [[http:// | + | |
<code php> | <code php> | ||
- | declare(strict_operators=1); | + | $a = ' |
+ | $b = 10; | ||
+ | $c = '9 eur'; | ||
- | 1.2 + 2; // float(3.2) | + | if (($a > $b) && ($b > $c) && ($c > $a)) { |
+ | // Unexpected | ||
+ | } | ||
</ | </ | ||
- | In this case, we're passing an '' | + | ==== Numeric string comparison ==== |
- | ==== Comparison operators ==== | + | Non-strict comparison uses a " |
- | Comparison operators work on all scalar types. The types of both values need to match. | + | |
- | Non-scalar types only support the '' | + | 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. |
<code php> | <code php> | ||
- | " | + | $red = ' |
- | " | + | $purple = ' |
- | " | + | $red == $purple; // true |
- | " | + | |
- | " | + | |
- | + | ||
- | true > false; | + | |
- | true != 0; // TypeError(" | + | |
- | + | ||
- | [10] > []; // TypeError(" | + | |
- | [10] == []; // false | + | |
</ | </ | ||
- | The function | + | It may also cause issues with sorting, as the meaning |
- | + | ||
- | === Numeric string | + | |
- | Numeric strings are compared | + | |
<code php> | <code php> | ||
- | " | + | function sorted(array $arr) { |
- | (float)" | + | usort($arr, function($x, |
+ | return $arr; | ||
+ | } | ||
- | " | + | sorted([' |
+ | sorted([' | ||
+ | sorted([' | ||
</ | </ | ||
- | === Array comparison === | + | ==== Array comparison ==== |
- | Comparing two arrays will never throw a '' | + | |
- | The difference between using the '' | + | Using the '' |
- | Scalar values in the array are compared using both type and value, thus similar to the '' | + | In the following example '' |
<code php> | <code php> | ||
- | ['a' => ' | + | $a = ['x' => 1, 'y' => 22]; |
- | [' | + | $b = ['y' => 10, 'x' => 15]; |
- | ['a' => ' | + | $a > $b; // true |
+ | $a < $b; // true | ||
</ | </ | ||
- | === Object comparison === | + | The logic of relational operators other than '' |
- | Comparing two objects of different classes using the '' | + | |
- | <code php> | + | ==== Strict vs non-strict comparison of arrays ==== |
- | class Foo { | + | |
- | public $x; | + | |
- | + | ||
- | public function __construct($x) { | + | |
- | $this->x = $x; | + | |
- | } | + | |
- | } | + | |
- | class FooBar extends Foo {} | + | Strict |
- | + | ||
- | (new Foo(10)) == (new Foo(10)); | + | |
- | (new Foo(10)) == (new Foo(99)); | + | |
- | (new Foo(10)) === (new Foo(10)); | + | |
- | + | ||
- | (new Foo(10)) == (new FooBar(11)); | + | |
- | (new Foo(10)) === (new FooBar(11)); | + | |
- | </ | + | |
- | + | ||
- | Comparing two objects of the same class will with these operators check the properties of the objects. By default, properties are compared | + | |
- | + | ||
- | ==== Arithmetic operators ==== | + | |
- | Arithmetic operators will only work with integers and floats. Using operands of any other type will result in a '' | + | |
- | + | ||
- | In strict | + | |
- | + | ||
- | The '' | + | |
- | + | ||
- | ==== Incrementing/ | + | |
- | The incrementing/ | + | |
- | + | ||
- | The function of these operators for integers and floats remains unchanged. | + | |
- | + | ||
- | === Incrementing strings === | + | |
- | The '' | + | |
<code php> | <code php> | ||
- | $a = " | + | ['a' |
- | ++$a; // " | + | |
- | ++$a; // " | + | |
- | ++$a; // " | + | |
</ | </ | ||
- | ==== Bitwise Operators ==== | + | 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. |
- | Bitwise operators expect both parameters | + | |
- | Using strings for '' | + | ==== Inconsistent behavior ==== |
- | ==== String | + | Operators |
- | The concatenation operator '' | + | |
- | Integers, floats and objects | + | * Cast (silent) |
+ | * Cast with notice / warning | ||
+ | * Cast with catchable error (fatal) | ||
+ | * Operator specific notice / warning | ||
+ | * Operator specific error (fatal) | ||
+ | * No operation | ||
- | ==== Logical Operators ==== | + | Please take a look at this [[https:// |
- | The function | + | |
- | ==== Switch control structure | + | ===== Backward Incompatible Changes |
- | When strict-type checking for operators is enabled, the '' | + | |
- | <code php> | + | None known. As this RFC proposes a new directive, it should only affect |
- | function match($value) | + | |
- | { | + | |
- | switch ($value) { | + | |
- | case [" | + | |
- | return " | + | |
- | break; | + | |
- | case null: | + | |
- | return " | + | |
- | break; | + | |
- | case 0: | + | |
- | return " | + | |
- | break; | + | |
- | case " | + | |
- | return " | + | |
- | break; | + | |
- | case " | + | |
- | return " | + | |
- | break; | + | |
- | default: | + | |
- | throw new Exception(" | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | match([" | + | |
- | match(0); | + | |
- | match(" | + | |
- | match(" | + | |
- | </code> | + | |
- | + | ||
- | + | ||
- | ===== Backward Incompatible Changes ===== | + | |
- | Since the strict type-checking for operators is off by default and must be explicitly used, it does not break backward-compatibility. | + | |
===== Proposed PHP Version ===== | ===== Proposed PHP Version ===== | ||
- | This is proposed for the next minor version of PHP, currently PHP 7.4. | ||
- | ===== FAQ ===== | + | This is proposed for PHP 8.0. |
- | ==== Why does == and != throw a TypeError instead of returning false? | + | ===== Unaffected PHP Functionality |
- | In other dynamically typed languages, like Python and Ruby, the '' | + | |
- | PHP already has the '' | + | This RFC |
- | ==== Why does the concatenation operator cast, but arithmetic operators don't? ==== | + | * Does not affect any functionality concerning explicit typecasting. |
- | The concatenation operator will cast integers, floats and //(when '' | + | * Is largely unaffected by other proposals like [[rfc/string_to_number_comparison|PHP RFC: Saner string to number comparisons]] that focus on improving type juggling at the cost of breaking BC. |
- | Arithmetic operators won't cast strings to an integer or float, because not all strings can be properly represented as a number and a '' | + | ===== FAQ ===== |
- | Both the concatenation operator and arithmetic operators throw a '' | + | This RFC has an [[rfc/strict_operators/ |
- | Using a boolean or null as operand for both concatenation and arithmetic operators also throws a '' | + | * [[rfc/ |
- | + | * [[rfc/ | |
- | ==== Will comparing a number to a numeric string work with strict operators? ==== | + | * [[rfc/strict_operators/faq# |
- | No, this will throw a '' | + | * [[rfc/strict_operators/ |
- | + | * [[rfc/ | |
- | ==== Are build-in functions affected by strict_operators? | + | * [[rfc/ |
- | No. Only operators (including the '' | + | |
- | + | * [[rfc/strict_operators/ | |
- | Specifically '' | + | * [[rfc/ |
- | + | * [[rfc/ | |
- | ==== Can relational operators be allowed for arrays? | + | * [[rfc/strict_operators/ |
- | If both arrays must have the same keys in the same order, using ''<'' | + | * [[rfc/ |
- | + | * [[rfc/ | |
- | Throwing a '' | + | * [[rfc/strict_operators/ |
- | + | ||
- | ==== Are there cases where a statement doesn' | + | |
- | Yes, there are 3 cases where a comparison works differently in strict operators mode. | + | |
- | + | ||
- | | + | |
- | * When comparing two arrays (or objects) no typecasting will be performed. So '' | + | |
- | * With strict operators, the '' | + | |
- | + | ||
- | This may cause issues when copying PHP code from one place to another. Also, programmers may intentionally rely on the current semantics when comparing numeric strings. However, as shown in the examples, code that exploits this behavior will likely have unintended side effects. Avoiding this behavior is not a side effect, but the purpose for using the '' | + | |
- | + | ||
- | ==== Is it possible to limit the effect of the directive to only throwing a TypeError? ==== | + | |
- | No, not in a reasonable way. Many of the motivating examples marked as //unexpected behavior// are caused by modifying the effect of a comparison based on the values of the operands. Keeping that unchanged would defeat the purpose of the RFC. | + | |
- | + | ||
- | An alternative would be to disallow strings, arrays, and objects as operands for comparison operators, requiring comparison to use functions like '' | + | |
- | + | ||
- | ==== Will this directive disable type juggling altogether? | + | |
- | No. Operators can still typecast under the given conditions. For instance, the concatenation ('' | + | |
- | + | ||
- | Typecasting is also done in other places: '' | + | |
- | + | ||
- | This RFC limits the scope to operators. | + | |
- | + | ||
- | ==== Why is switch affected? It's not an operator. ==== | + | |
- | Internally the '' | + | |
- | + | ||
- | However, since '' | + | |
- | + | ||
- | + | ||
- | ===== Unaffected PHP Functionality ===== | + | |
- | This RFC | + | |
- | + | ||
- | * Does not affect any functionality concerning explicit typecasting. | + | |
- | * Does not affect variable casting that occurs in (double-quoted) strings. | + | |
- | * Is largely unaffected by other proposals like [[rfc: | + | |
===== Implementation ===== | ===== Implementation ===== | ||
- | [[https:// | + | https:// |
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
- | **Voting ended prematurely and will be reopened at a later stage targeting PHP 8.** | ||
- | |||
- | * Primary vote; Add the '' | ||
- | * Secondary vote: Should '' | ||
- | <doodle title=" | + | Primary vote: Accept the RFC and merge the patch? Yes/No. Requires a 2/3 majority. |
- | | + | |
- | | + | |
- | </doodle> | + | |
- | <doodle title=" | ||
- | * Yes | ||
- | * No | ||
- | </ |
rfc/strict_operators.txt · Last modified: 2020/07/15 14:27 by jasny