Empty String Comparison to Zero Gives Different Result in PHP 8 Than in Previous Versions

Empty string comparison to zero gives different result in PHP 8 than in previous versions

You are right, this is a major change.

As with any version upgrade, you can find a guide to Migrating to PHP 8.0 in the official PHP manual. If you click on Backward Incompatible Changes you will see that this change is the very first thing on that page:

Non-strict comparisons between numbers and non-numeric strings now work by casting the number to string and comparing the strings.

As well as an example in the next sentence, there is a before-and-after comparison table which includes the exact example you gave:

Comparison: 0 == ""; Before: true; After: false

If you have code that was relying on the old behaviour, you will need to update it to be more explicit about the values expected. For instance, all of the following work in all versions of PHP:

if ( $value === 0 || $value === "" ) { ... }
if ( (string)$zero === "" ) { ... }
if ( (int)$emptyString === 0 ) { ... }

For more background on the change, you can read the original proposal here: PHP RFC: Saner string to number comparisons

Unexpected result of greater than or less than comparison on PHP 8

There is no obviously correct result for a comparison between a string and a number. In many languages, it would just give an error; in others, including PHP, the language tries to make sense of it by converting both operands to the same type, but this involves a judgement of which type to "prefer".


Historically, PHP has preferred comparing numbers to comparing strings: it treated "U0M262" > 100000 as (int)"U0M262" > 100000. Since (int)"U0M262" has no obvious value, it is evaluated as 0, and the expression becomes 0 > 100000, which is false.

As of PHP 8, this behaviour has changed and PHP now only uses a numeric comparison for "numeric strings", e.g. "42" clearly "looks like" 42.

Since "U0M262" doesn't fit the requirements for a numeric string, "U0M262" > 100000 is now treated as "U0M262" > (string)100000. This does a byte-wise comparison of the sort order for the two strings, and finds that since "U" comes after "1" in ASCII (and any ASCII-derived encoding, including UTF-8), the result is true.


Because of how ASCII (and compatible encodings such as UTF-8) is arranged:

  • A string starting with a control character or space will be "less than" any number
  • A string starting with a letter will be "more than" any number
  • A string starting with any of "! " # $ % & ' ( ) * + , - . /" will be "less than" any number
  • For a string starting with a digit, you need to look at the individual bytes
  • Any other string will be "more than" any number

As ever, you can tell PHP which comparison you intended, and get the correct behaviour in all versions, using explicit casts:

var_dump((int)"U0M262" > (int)100000); // bool(false)
var_dump((string)"U0M262" > (string)100000); // bool(true)

(Obviously, this makes no sense if you're hard-coding both sides anyway, but assuming one or both is a variable, this is how you'd do it.)

PHP 8.0 changes how loose comparison works

PHP Documentation maintainer here, PHP 8 did change the semantics and this is shown in the migration guide. However other parts of the documentations are still lagging behind as we don't have the manpower/time for editing and documenting all the changes related to PHP 8.

So this is not a bug and more a fact that the current type juggling page is out of date in regards to PHP 8.0.

It is possible to contribute to the docs via a Pull Request to the GitHub repository.

substr returns an empty string when string is less than start characters long in PHP 8

It seems like this was an intentional change as mentioned in
https://php.watch/versions/8.0/substr-out-of-bounds

and implemented here:

https://github.com/php/php-src/pull/6182

Migration to PHP 8.1 - how to fix Deprecated Passing null to parameter error - rename build in functions

Firstly, two things to bear in mind:

  1. PHP 8.1 deprecates these calls, it does not make them errors. The purpose of deprecation is to give authors advance notice to fix their code, so you and the authors of libraries you use have until PHP 9.0 comes out to fix things. So, don't panic that not everything is fixed right away, and be patient with library maintainers, who will get to this in their own time.
  2. The quick fix in most cases is to use the null coalescing operator to provide a default value as appropriate, so you don't need a long null check around every use. For instance, htmlspecialchars($something) can be replaced with htmlspecialchars($something ?? '')

Next, some options:

  • Depending how many cases you have, you may be able to just fix them manually a few at a time, either adding ?? '' or fixing a logic bug where you weren't expecting a null anyway.
  • Create custom functions like nullable_htmlspecialchars and do a straight-forward find and replace in your code.
  • Create custom namespaced functions like nullableoverride\htmlspecialchars; then in any file where you add use function nullableoverride\htmlspecialchars; that function will be used instead of the built-in one. This has to be added in each file, though, so you may need a tool to automate adding it.
  • Use Rector to automate adding ?? '' to appropriate function calls, so you don't have to edit them all by hand. Unfortunately, there doesn't seem to be a built-in rule for this (yet), so you'd have to learn to write your own.
  • Possibly simpler, depending on your skills, use a regular expression find-and-replace to add the ?? '' to simple cases.

Why does someString == 0 evaluate to true in PHP

When using the comparison (==) operator strings will be converted to an integer when compared to another integer. This is because of type juggling in PHP. So "someString" evaluates to zero because it is converted to an integer and has no leading digits. If you use the the identical operator (===) type conversions are not done so "someString" is treated a literal string and your statement will then evaluate to false.

The following will evaluate to false when type juggling is performed. Everything else will be evaluated as true:

  • "" (an empty string)
  • 0 (0 as an integer)
  • 0.0 (0 as a float)
  • "0" (0 as a string)
  • NULL
  • FALSE
  • array() (an empty array)
  • $var; (a variable declared, but without a value)

empty() returns true if a zero float value was passed as string

This zero/empty condition works the same way in both PHP 7 and 8 versions:

if (empty($var) || (is_numeric($var) && (float)$var == 0))

It checks if $var is any of the following:
not set, null, (bool)false, (int)0, (float)0.00, (string)"", (string)"0", (string)"0.00", or (array)[]

And to substitute empty():

  // Checks if variable is not set, null, (bool)false, (int)0, (float)0.00, (string)"", (string)"0", (string)"0.00", (array)[], or array with nil nodes
function nil(&$var) { // https://www.dictionary.com/browse/nil
return (empty($var) || (is_numeric($var) && (float)$var == 0));
}

To be used like:

if (nil($var)) {
echo 'The value is either not set, an empty string, empty array, null, or equals zero.';
}

It can be expanded to check subnodes for arrays as well:

  // Checks if variable is not set, null, (bool)false, (int)0, (float)0.00, (string)"", (string)"0", (string)"0.00", (array)[], or array with nil nodes
function nil(&$var) {
if (is_array($var)) {
foreach ($var as $node) {
if (!nil($node)) return !1;
}
}
return (empty($var) || (is_numeric($var) && (float)$var == 0));
}


Related Topics



Leave a reply



Submit