Search code examples
phpusortphp-8

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


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?


Solution

  • 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