PHP 8 になるまでの sort は相当ヤバい

1.6K Views

September 28, 24

スライド概要

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

PHP 8 になるまでの sort は相当ヤバい 2024.09.28 PHP Conference Okinawa 2024 荒瀬 泰輔 #phpcon_okinawa #track_a

2.

荒瀬 泰輔 @at_taisuke PHPer歴5年くらい サイボウズかき氷部の部長 2024月刊ぺちこん参加 北海道 東京 香川 福岡 沖縄 2

4.

沖縄のおすすめかき氷 4

5.

かんな +plus さん 東京の三宿の人気店「和キッチン かんな」の分店 沖縄ならではの食材を使ったかき 氷 写真は「イエロー」(パッション フルーツとマンゴーのかき氷) 5

6.

avocafe さん アボカド料理店 クリーミーなアボカドはかき氷 にしても美味しい! 写真は「アボカドキャラメルナッ ツ」 6

7.

行きたいお店 天の群星(てぃんのむりぶし) PLUS MINUS 琉冰(Ryu-Pin) 皆さんのおすすめかき氷、沖縄ぜんざいのお店あったら #phpcon_okinawa で教えてください! 7

8.

本題 8

9.

…の前に 9

10.

[悲報] タイトル、半分釣りだった 10

11.

結論 数値と文字列をごちゃ混ぜにした配列を のがヤバい とはいえ sort しようとしている PHP 8 より前の比較演算もヤバいのでどっちもヤバい sort をするならヤバいコードは掃討しましょう 11

12.

Agenda 当初話す予定だったブログ記事の紹介 x でのご指摘を受けての再調査 PHP の比較の挙動 おまけ PHP の比較 true or false ? クイズ 「数値から始まる文字列」について 12

13.

当初話す予定だったブログ記事の紹介 13

14.

当初話す予定だったブログ記事の紹介 事前にこの場で話す内容をブログ記事に してました CYBOZU SUMMER BLOG FES '24」 という企画で 8/15 に公開 PHP 8 になるまでの sort は相当ヤバい https://zenn.dev/t_arase/articles/0a12bfd6a76e25 「 14

15.

PHP 7の比較に矛盾が生じてるぞ! 当初話す予定だったブログ記事の紹介 <?php // PHP 7.4.33 var_dump(11 < '12'); // true var_dump('12' < 'a1'); // true var_dump('a1' < 10); // true var_dump(10 < 11); // true 15

16.

当初話す予定だったブログ記事の紹介 https://commons.wikimedia.org/wiki/File:Impossible_staircase.svg より 16

17.

sort の結果もおかしいぞ! 当初話す予定だったブログ記事の紹介 <?php $arr1 = [11, 10, '12', 'a1']; sort($arr1); var_export($arr1); echo PHP_EOL; $arr2 = ['12', 'a1', 11, 10]; sort($arr2); var_export($arr2); echo PHP_EOL; var_dump($arr1 === $arr2); 17

18.

当初話す予定だったブログ記事の紹介 # PHP 7.4.33 array ( 0 => 10, 1 => 11, 2 => '12', 3 => 'a1', ) array ( 0 => '12', 1 => 'a1', 2 => 10, 3 => 11, ) bool(false) // var_dump($arr1 === $arr2) 18

19.

PHP 8 でこの矛盾が解消されたぞ! 当初話す予定だったブログ記事の紹介 <?php // PHP 8.0.0 var_dump(11 < '12'); // true var_dump('12' < 'a1'); // true var_dump('a1' < 10); // false ここが変わった var_dump(10 < 11); // true 19

20.

PHP 8 で比較演算の挙動が変わったため 当初話す予定だったブログ記事の紹介 数値と「数値形式ではない文字列」を比較する際 PHP 7 まで:文字列を数値に変換してから比較 PHP 8 から:数値を文字列に変換してから比較 するようになった 数値と「数値形式の文字列」を比較する際は今まで通り文字列を数値 に変換してから比較する 'hoge' == 0; // true PHP 7.4.33 まで 'hoge' == 0; // false PHP 8.0.0 から PHP RFC: Saner string to number comparisons https://wiki.php.net/rfc/string_to_number_comparison より 20

21.

当初話す予定だったブログ記事の紹介 sort 順も正しくなった! // PHP 8.0.0 array ( 0 => 10, 1 => 11, 2 => '12', 3 => 'a1', ) array ( 0 => 10, 1 => 11, 2 => '12', 3 => 'a1', ) bool(true) 21

22.

当初話す予定だったブログ記事の紹介 PHP 8 までの sort は相当おかしい! ヨシ! 22

23.

x でのご指摘を受けての再調査 23

24.

x でのご指摘を受けての再調査 記事公開後、コメントをいただきました 文字列と数値混じりの sortができるように変わったと誤解しそう なので、できない例も載せても良いのではと思いました var_dump(11 < '12'); var_dump('12' < '3a'); var_dump('3a' < '4'); var_dump('4' < 11); @tompng さんありがとうございました! https://x.com/at_taisuke/status/1824000184231321736 ぺん! 24

25.

x でのご指摘を受けての再調査 確かに な ? … PHP 8 でも比較がおかしいよう // PHP 8.0.0 var_dump(11 < '12'); // true var_dump('12' < '3a'); // true var_dump('3a' < '4'); // true var_dump('4' < 11); // true 25

26.

x でのご指摘を受けての再調査 $arr1 = [11, '4', '12', '3a']; sort($arr1); var_export($arr1); $arr2 = ['12', '3a', 11, '4']; sort($arr2); var_export($arr2); var_dump($arr1 === $arr2); array ( 0 => '4', 1 => 11, 2 => '12', 3 => '3a', ) array ( 0 => 11, 1 => '12', 2 => '3a', 3 => '4', ) bool(false) 26

27.

x でのご指摘を受けての再調査 自分の遭遇した例がたまたま ダメなパターンもある PHP 8 でいい感じになっただけで、 どうしてこういう結果になるのか再度調査しなくては 27

28.

PHP の比較の挙動 28

29.

PHP の比較の挙動 文字列同士の比較 先頭の文字から順にバイナリで比較する 'a' < 'b'; // true 'a' < 'A'; // false 'a ' < 'a1' // true 29

30.

PHP の比較の挙動 おさらい: PHP 8 での比較で変わった点 数値と「数値形式ではない文字列」を比較する際 PHP 7 まで:文字列を数値に変換してから比較 PHP 8 から:数値を文字列に変換してから比較 するようになった 数値と「数値形式の文字列」を比較する際は今まで通り文字列を数値 に変換してから比較する 'hoge' == 0; // true PHP 7.4.33 まで 'hoge' == 0; // false PHP 8.0.0 から 30

31.

PHP の比較の挙動 「数値形式の文字列」について PHP には「数値形式の文字列(numeric strings)」という概念があ る PHP 7.4.33 までは「0個以上のスペース + 数値」がこれに該当した が、 PHP 8 からは「0個以上のスペース + 数値 + 0個以上のスペース」に 変更になった // 数値形式の文字列の例 '11' ' 11 ' // 後ろにスペースがあるため、PHP 8 より前ではただの文字列扱いだった '000011' '11.0' '+11.0E0' 31

32.

PHP の比較の挙動 コメントをもらった例を見てみる 11 < '12'; // true // 数値と「数値形式の文字列」の比較のため、「数値形式の文字列」を数値として扱う '12' < '3a'; // true // 文字列同士の比較となり、最初の1文字目のバイナリで比較される '3a' < '4'; // true // 文字列同士の比較となり、最初の1文字目のバイナリで比較される '4' < 11; // true // 数値と「数値形式の文字列」の比較のため、「数値形式の文字列」を数値として扱う 何と比較されるかで、その値が文字列として扱われるか、数値として 扱われるか変わるため、 して順番をつけることができない つまりこんな値を sort sort しようとするのが悪い 32

33.

結論 数値と文字列をごちゃ混ぜにした配列を のがヤバい とはいえ sort しようとしている PHP 8 より前の比較演算もヤバいのでどっちもヤバい sort をするならヤバいコードはそーっとしておかずに掃討しまし ょう 33

34.

ここまで話せたらあとはおまけ 34

35.

PHP の比較 true or false ? クイズ 35

36.

PHP の比較 true or false ? クイズ PHP 8.0.0 4 全て 以降の結果で考えてください 全部で 問です 36

37.

PHP の比較 true or false ? クイズ 10 < 11 true or false ? 37

38.

PHP の比較 true or false ? クイズ true 数値どうしの比較 10 < 11 38

39.

PHP の比較 true or false ? クイズ 1 < '1 ' true or false ? 39

40.

PHP の比較 true or false ? クイズ false 数値と「数値形式の文字列」の比較のため、「数値形式の文字列」を 数値として扱う 1 < '1 ' 40

41.

PHP の比較 true or false ? クイズ '1a' < 10 true or false ? 41

42.

PHP の比較 true or false ? クイズ false 1 文字列どうしの比較となり、数値を文字列として扱い 文字目から順 にバイナリで比較される '1a' < 10 PHP 7.4.33 ちなみに までだと '1a' が数値として扱われ、数値どう しの比較になり、 true になる 42

43.

PHP の比較 true or false ? クイズ '123' < '45 ' true or false ? 43

44.

PHP の比較 true or false ? クイズ false 「数値形式の文字列」どうしの比較になり、両方を数値として扱う '123' < '45 ' PHP 7.4.33 ちなみに までだと '45 ' が文字列として扱われ、最初 の文字の '1' と '4' の比較の結果、 true になる 44

45.

「数値から始まる文字列」について 45

46.

「数値から始まる文字列」について 「数値から始まる文字列」について PHP には「数値から始まる文字列(leading-numeric strings)」 という概念も存在する + 「数値から始まる文字列」は「数値形式の文字列」 任意の文字列 で構成される '123abc' これは比較演算の際にはただの文字列と同じ扱いになる '123' < '45abc' // true 46

47.

「数値から始まる文字列」について 「数値から始まる文字列」について 「数値から始まる文字列」は数値にキャストした場合には「数値形式 の文字列」部分を数値に変換した値になる var_dump((int) '45abc'); // 45 var_dump((int) 'abc45'); // 0 「数値から始まる文字列」は 数値型が宣言されている引数に入れる ことはできない。 function foo(int $i) { var_dump($i); } foo("123 "); // int(123) foo("123abc"); // TypeError 47

48.

「数値から始まる文字列」について 「数値から始まる文字列」かどうかの見 分け方 「数値から始まる文字列」かどうかは数値に足してみて 出るかどうかで判断できる。( ) PHP 8.3 warning が var_dump(123 + "123 "); // int(246) var_dump(123 + "123a"); // Warning: A non-numeric value encountered // int(246) 48

49.

PHP 7.4.33 までの「数値形式の文字列」 「数値から始まる文字列」について PHP 7.4.33 までは、数値の後ろにスペースがある場合は「数値から 始まる文字列」扱いとなっていた。 // PHP 7.4.33 var_dump(123 + "123 "); // Notice: A non well formed numeric value encountered // int(246) 49

50.

ご清聴ありがとうございました! 50