# RFC: Strict operators directive

- Status: Withdrawn
- Authors: Arnold Daniels, jasny@php.net
- Date: 2020-07-06 (first version: 2019-05-25)
- Version: 1.5
- Original: https://wiki.php.net/rfc/strict_operators
- Page: https://externals.io/rfc/strict_operators
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Discussions on the mailing list

- [\[RFC\] Strict operators directive](https://externals.io/message/106054.md) — started on 2019-06-25, 27 messages
- [\[VOTE\] Strict operators directive](https://externals.io/message/106189.md) — started on 2019-07-09, 10 messages
- [\[RFC\] Strict operators directive](https://externals.io/message/110172.md) — started on 2020-05-15, 6 messages
- [\[RFC\]\[DISCUSSION\] Strict operators directive](https://externals.io/message/110848.md) — started on 2020-07-06, 5 messages

---

## Introduction

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.

## Proposal

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;

- Operators may perform type conversion, but not type juggling: 
    - 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 `TypeError` for unsupported types

In 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](http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.1.2) 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.

## Details for operands

This section details which types operands support, and what type juggling will be supported (if any) for them.

### Arithmetic operators

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.

### Incrementing/Decrementing operators

The incrementing/decrementing operators `++` and `--` will only support `int` or `float` operands. Attempting to use an unsupported operand will throw a `TypeError`.

### Bitwise Operators

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

### Comparison operators

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.

### String concatenation

The concatenation operator `.` will only support concatenating `null`, `int`, `float`, `string`, and [stringable object](https://externals.io/rfc/stringable) operands.

If any of the operands is a `bool`, `array`, `resource`, or non-stringable object, a `TypeError`will be thrown.

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

i.e. this code

```php
echo "He drank some $juice juice.";
```

has the same rules as using the string concatenation operator.

```php
echo "He drank some " . $juice . " juice.";
```

### Logical Operators

There a no changes to the logical operators `&&`, `||`, `!`, `and`, `or`, and `xor`.

### Ternary / Null Coalescing Operator

There a no changes to the ternary (`?:`) and null coalescing (`??`) operator.

## Motivating examples

The following section demonstrates code that is written logical, but has illogical results.

### Mixed type comparison

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.

```php
$a = '42';
$b = 10;
$c = '9 eur';
 
if (($a > $b) && ($b > $c) && ($c > $a)) {
   // Unexpected 
}
```

### Numeric string comparison

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.

```php
$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).

```php
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']
```

### Array comparison

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`

```php
$a = ['x' => 1, 'y' => 22];
$b = ['y' => 10, 'x' => 15];
 
$a > $b; // true
$a < $b; // true
```

### Strict vs non-strict comparison of arrays

Strict comparison requires that arrays have keys occurring in the same order, while non-strict comparison allows out-of-order keys.

```php
['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.

### Inconsistent behavior

Operators can do any of the following for unsupported operands

- Cast (silent)
- Cast with notice / warning
- Cast with catchable error (fatal)
- Operator specific notice / warning
- Operator specific error (fatal)
- No operation

Please take a look at this [list of all combinations of operators and operands](https://gist.github.com/jasny/bfd711844a8876f8206ed21357e2e2da).

## Backward Incompatible Changes

None known. As this RFC proposes a new directive, it should only affect code is new or updated to use the strict\_operators directive.

## Proposed PHP Version

This is proposed for PHP 8.0.

## Unaffected PHP Functionality

This RFC

- Does not affect any functionality concerning explicit typecasting.
- Is largely unaffected by other proposals like [PHP RFC: Saner string to number comparisons](https://externals.io/rfc/string_to_number_comparison) that focus on improving type juggling at the cost of breaking BC.

## FAQ

This RFC has an [FAQ](https://wiki.php.net/rfc/strict_operators/faq) that answers some questions

- [What has been changed since the initial proposal?](https://wiki.php.net/rfc/strict_operators/faq#what_has_been_changed_since_the_initial_proposal)
- [Why use a directive instead of applying this behavior as default?](https://wiki.php.net/rfc/strict_operators/faq#why_use_a_directive_instead_of_applying_this_behavior_as_default)
- [Why does == and != only support int and float operands?](https://wiki.php.net/rfc/strict_operators/faq#why_does_and_only_support_int_and_float_operands)
- [Why don't comparison operators support strings?](https://wiki.php.net/rfc/strict_operators/faq#why_don_t_comparison_operators_support_strings)
- [Why does the concatenation operator cast, but arithmetic operators don't?](https://wiki.php.net/rfc/strict_operators/faq#why_does_the_concatenation_operator_cast_but_arithmetic_operators_don_t)
- [Will comparing a number to a numeric string work with strict\_operators?](https://wiki.php.net/rfc/strict_operators/faq#will_comparing_a_number_to_a_numeric_string_work_with_strict_operators)
- [How can arrays be compared as unsorted hashmaps?](https://wiki.php.net/rfc/strict_operators/faq#how_can_arrays_be_compared_as_unsorted_hashmaps)
- [How can objects be compared by property?](https://wiki.php.net/rfc/strict_operators/faq#how_can_objects_be_compared_by_property)
- [Why isn't is allowed to increment strings with strict\_operators?](https://wiki.php.net/rfc/strict_operators/faq#why_isn_t_is_allowed_to_increment_strings_with_strict_operators)
- [Are built-in functions affected by strict\_operators?](https://wiki.php.net/rfc/strict_operators/faq#are_built-in_functions_affected_by_strict_operators)
- [Can relational operators be allowed for arrays?](https://wiki.php.net/rfc/strict_operators/faq#can_relational_operators_be_allowed_for_arrays)
- [Why is switch not affected?](https://wiki.php.net/rfc/strict_operators/faq#why_is_switch_not_affected)
- [Are there cases where a statement doesn't throw a TypeError but yields a different result?](https://wiki.php.net/rfc/strict_operators/faq#are_there_cases_where_a_statement_doesn_t_throw_a_typeerror_but_yields_a_different_result)
- [Will this directive disable type juggling altogether?](https://wiki.php.net/rfc/strict_operators/faq#will_this_directive_disable_type_juggling_altogether)

## Implementation

<https://github.com/php/php-src/pull/4375>

## Proposed Voting Choices

Primary vote: Accept the RFC and merge the patch? Yes/No. Requires a 2/3 majority.
