PHP5.6までとPHP7とPHP8でソートの挙動が違う

沖縄チームのキリです。これからお見せするのはちょっとした恐怖映像です。まずはこちらのコードをご覧ください。

<?php

$a = array(2, "a", "11", 2);
$b = array(2, "11", "a", 2);

sort($a);
sort($b);

var_dump($a, $b);

それでは次にこちらのコードをPHP4.3~PHP8.3で実行した結果をご覧いただこう。

https://3v4l.org/2DBVP

おわかりいただけただろうか。

$aと$bは中身は同じですが並びが違う配列です。中身は同じなのでソートにかけると同じ結果になりそうですが、PHP5.6以前とPHP7では$aと$bのソート結果が異なっています。PHP7の動きが特に気持ち悪いですね。$bは隣の要素同士を比べた結果並び替え済みと判断されたのでしょうか。PHP8はさすが、直感的な結果を返しているように思います。

この挙動を調べるきっかけとなったのはPHPドキュメントの以下のコメントでした。

https://www.php.net/manual/ja/language.operators.comparison.php#65863

Note: according to the spec, PHP’s comparison operators are not transitive. For example, the following are all true in PHP5:

“11” < “a” < 2 < “11”

一部引用するとPHP5ではこれらの比較がすべて真として成り立っているということです。気になったので'11' < 'a'と’a’ < 2と2 < ’11’の比較結果も出力してみました。検証に利用したサービスは3v4lというサイトで、サイト名の”3v4l”は”EVAL”をLeet表記したものです。aの代わりに@を書くようなアレですね。ちなみに私がこの表記法をLeetと呼ぶことを知ったのはダライアスバーストのHello 31337というゲーム楽曲からです。

おやおや、PHP7.xまでとPHP8.xで’a’ < 2の結果が違います。PHP8から比較方法が変わったようです。

https://www.php.net/manual/ja/migration80.incompatible.php#migration80.incompatible.core.string-number-comparision

(厳密でないやり方で)数値と非数値文字列を比較する場合、 数値を文字列にキャストし、文字列と比較するようになりました。

もしPHP5.6やPHP7.4等の古い案件でPHPのソートを扱っていて、バージョンアップ対応を迫られた場合はこのような挙動の違いがあるので注意しましょう。