Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what happened in PHP8.0.0 to break usort(...(int)(strlen($a)<strlen($b)));?

Tags:

php

php-8

usort

the code

<?php
$consts = get_defined_constants();
$consts = array_keys($consts);
usort($consts,function($a,$b){return (int)(strlen($a)<strlen($b));});
foreach($consts as $const){
    echo strlen($const).": ".$const."\n";
}

will, prior to PHP 8.0.0, print all defined constants from longest to shortest, as i expected. 7.3.13 starts with

62: SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE
62: SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE
60: SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_SENSITIVE
60: SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_SENSITIVE
51: SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_STRPREFIX

but i have no idea what PHP8.0.0 did, it's output starts with:

9: E_WARNING
21: FILTER_FLAG_STRIP_LOW
7: E_ERROR
26: FILTER_FLAG_STRIP_BACKTICK

you can see it yourself on 3v4l: https://3v4l.org/MP2IF

so what happened in PHP 8.0.0 to break this code?

like image 567
hanshenrik Avatar asked Dec 20 '20 17:12

hanshenrik


3 Answers

Most of the other answers here are focused around how to fix the problem, but I thought I'd try and explain why this changed in PHP 8, which I think is what you're interested in.

PHP 8 introduced the Stable Sorting RFC, which (as it sounds) means that all sorting functions in PHP are now "stable". More details about this in the link.

The other answers covered this well already, but your function returns either zero or a number greater than zero. Previous implementations of sorting in PHP (in all versions lower than 8) considered zero and a negative number the same; as the RFC above mentions, the check was simply for a number greater than zero, or not. Returning zero would mean that these elements were treated the same as the case where $a < $b.

PHP introduced a deprecation warning so that a lot of sorting implementations that return booleans would still work. The RFC gives some more details on this, but importantly it means PHP 8 is still backwards compatible with them (hence this being a deprecation notice, rather than a warning). The edge-case here is that while your function effectively returns a boolean - 0 for the same length, and 1 for the case where $a < $b - because you cast this to an integer, the backwards-compatibility check in PHP 8 does not catch it, and so all "equal" elements are considered as though $a < $b

Compare:

function($a, $b) { return (int) (strlen($a) < strlen($b)); }

As in the question - works correctly in PHP <8, but raises no deprecation notice. https://3v4l.org/MP2IF


function($a, $b) { return strlen($a) < strlen($b); }

Returns a boolean, so the backwards-compatibility check in PHP 8 works correctly. But a deprecation notice is now raised. https://3v4l.org/fWR2Y


function($a, $b) { return strlen($b) <=> strlen($a); }

The "correct" solution, working correctly in all versions (at least since the spaceship operator was introduced). https://3v4l.org/6XRYW

like image 188
iainn Avatar answered Nov 20 '22 22:11

iainn


It's because you are returning either 0 or 1, not -1. You should use the spaceship operator instead: https://3v4l.org/PLYAP

usort($consts, function($a, $b) {
    return strlen($b) <=> strlen($a);
});

0 should be returned in case both sides are equal.

like image 6
HTMHell Avatar answered Nov 20 '22 22:11

HTMHell


I don't know what changed internally to cause this difference, but your sorting callback is a bit funky. It should only be returning 0 if $a and $b are functionally "equal" (in this case, if they have the same string lengths). Otherwise it should return 1 if $a should sort before $b, or -1 otherwise. If I tweak your callback appropriately, I get expected output.

usort($consts, function($a, $b) {
    $aLen = strlen($a);
    $bLen = strlen($b);
    if ($aLen === $bLen) {
        return 0;
    }
    return $aLen < $bLen ? 1 : -1;
});

3v4l: https://3v4l.org/UScFO

like image 3
Garrett Albright Avatar answered Nov 20 '22 20:11

Garrett Albright