PHP RFC: Strict operators directive


What has been changed since the initial proposal?

  • Comparison operators == !=, >, >=, <, <= and <=> with non-numeric operands will throw a TypeError.
  • Variable parsing in strings specified in double quotes and with heredoc, is also affected.
  • Concatenation operation (using .) on null does not throw a TypeError, but will cast null to an empty string.
  • switch is not affected by this directive.

Why use a directive instead of applying this behavior as default?

Making significant changes to the behavior of operators has severe consequences to backward compatibility. Additionally, there is a significant group of people who are in favor of the current method of type juggling. Following the rationale of PHP RFC: Scalar Type Declarations; an optional directive ensures backward compatibility and allows people to choose the type checking model that suits them best.

Why does == and != only support int and float operands?

The main difference between the equal (==) and identical (===) operator, is that == doesn't perform type casting. By only disabling type casting with strict_operators, == would be similar to === for scalar types.

This RFC tries to avoid behaviorial changes based on the strict_operators directives.

The == and != operators compares two arrays as unsorted hashmaps. This is a useful feature. However, it's currently tied in with type juggling. Comparing the values of the array with type checking, similar to ===, would significatly change the behavior of an operation based on the strict_operators directive. Example; ['a' => null] == ['a' => 0] would result in true when the strict_operators is enabled and false otherwhise.

The == and != operators compares two objects by property. This is tied in with type juggling. Similar to arrays, comparing property values with === would change the behavior of the == operation.

Comparing two numeric strings using == and != will compare them as numbers. Disabling type juggling would change this behavior and make these operators the same as === and !=== in this case.

Why don't comparison operators support strings?

In many other languages, using ==, !=, <, >, <= and => with string operands performs an strcmp like operation. The <=> is even described as strcmp as operators. Why do these operators throw an exception with string operands when strict_operators is enabled?

It's common to use these operators when both operands are numeric strings. Having these statements return a different value based on the directive, could lead to issues, especially when source code is copy/pasted from an external codebase.

In case it concerns comparing text, it's better to use Collator::compare(). For non-collation related strings, like date strings, the strcmp function should be used.

Nikita Popov made the following argument:

Having $str1 < $str2 perform a strcmp() style comparison under strict_operators is surprising. I think that overall the use of lexicographical string comparisons is quite rare and should be performed using an explicit strcmp() call. More likely than not, writing $str1 < $str2 is a bug and should generate a TypeError. Of course, equality comparisons like $str1 == $str2 should still work, similar to the distinction you make for arrays.

Why does the concatenation operator cast, but arithmetic operators don't?

The concatenation operator will cast integers, floats and (when __toString is defined) objects to a string. This is a common use case and operands are always typecasted to a string. Integers and floats always have a proper string representation.

Arithmetic operators won't cast strings to an integer or float, because not all strings can be properly represented as a number and a TypeError must be thrown based on the operand type only, not the value.

Both the concatenation operator and arithmetic operators throw a TypeError for arrays, resources, and objects. Casting these to a string or int/float doesn't give a proper representation of the value of the operand.

Using a boolean or null as operand for both concatenation and arithmetic operators also throws a TypeError. In most cases, the use of a boolean or null indicates an error as many functions return false or null in case of an error or when no result can be returned. This is different from the function returning an empty string or 0. strpos is a well-known example.

Will comparing a number to a numeric string work with strict_operators?

No, this will throw a TypeError. Users that use string operators need to explicitly typecast the string to an integer. If it concerns input data, for instance from $_POST, it's recommended to use the filter functions.

How can arrays be compared as unsorted hashmaps?

Arrays are sorted hashmaps in PHP but used as any type of collection like unsorted maps, lists, and sets. Only comparing as unsorted hashmap is supported using ==, while other cases require the use of functions like sort() and array_values().

Strict comparison of arrays as unsorted hashmaps currently isn't possible and requires sorting the array, prior to comparison.

With == unavailable when using strict_operators, sorting the array is the only option available.

$array1 === $array2;

Array functions might be added to compare arrays in different ways. But that's outside the scope of this RFC.

How can objects be compared by property?

With the == operator, two object instances are equal if they have the same attributes and values, and are instances of the same class. Values are compared with ==, applying type juggling. Using === will check if the objects are the same instance.

When using strict_operators, == with objects will always throw a TypeError. With == unavailable, get_object_vars needs to be used.

get_class($object1) === get_class($object2) && get_object_vars($object1) === get_object_vars($object2);

Why isn't is allowed to increment strings with strict_operators?

Nikita Popov made the following argument:

String increment seems like a pretty niche use case, and I believe that many people find the overflow behavior quite surprising. I think it may be better to forbid string increment under strict_operators.

Are built-in functions affected by strict_operators?

No. Only operators (including the case statement) in the source file that has declare(strict_operators=1) are affected. Functions defined elsewhere, including functions defined in extensions, are not affected.

Specifically sort() and in_array() will apply type jugging by default and require the use of the $sort_flags and $strict arguments to change the way the values are compared.

Can relational operators be allowed for arrays?

If both arrays must have the same keys in the same order, using < or > on two arrays can be useful. But, as shown in the examples, when this is not the case, these type of comparisons will yield unexpected results.

Throwing a TypeError only if the keys of the arrays don't match is not in line with this RFC. The behavior of an operator should not depend on the value of the operand, only on the type. Furthermore, a TypeError would be misplaced here, as some arrays would be accepted but others not, whereas a TypeError indicates no values of that type are accepted.

Why is switch not affected?

Using strict_operators will only result in cases where a TypeError is thrown. It will not affect the behavior, giving a different result.

Changing switch to compare using === would change the behavior based on the directive.

To use strict comparison, most switch statements can be rewritten to match statements.

Are there cases where a statement doesn't throw a TypeError but yields a different result?


Will this directive disable type juggling altogether?

No. Operators can still typecast under the given conditions. For instance, the concatenation (.) operator will cast an integer or float to a string, and boolean operators will cast any type of operand to a boolean.

Type juggling is also done in other places: if and while statements interpret expressions as boolean, and booleans and floats are cast to an integer when used as array keys. The switch statement will still use loose comparison (==).

This RFC is limited to the scope of operators.

rfc/strict_operators/faq.txt · Last modified: 2020/07/06 15:01 by jasny