Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

沖縄のおすすめかき氷 4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

本題 8

Slide 9

Slide 9 text

…の前に 9

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

PHP 7の比較に矛盾が生じてるぞ!

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

sort の結果もおかしいぞ!

Slide 18

Slide 18 text

# 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

Slide 19

Slide 19 text

PHP 8 でこの矛盾が解消されたぞ!

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

確かに 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 x でのご指摘を受けての再調査 25

Slide 26

Slide 26 text

$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)                                                  x でのご指摘を受けての再調査 26

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

PHP の比較の挙動 28

Slide 29

Slide 29 text

文字列同士の比較 先頭の文字から順に Unicode 値を使い辞書的に比較する 'a' < 'b'; // true 'a' < 'A'; // false 'a ' < 'a1' // true PHP の比較の挙動 29

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

「数値から始まる文字列」について 「数値から始まる文字列」は数値にキャストした場合には「数値形式 の文字列」部分を数値に変換した値になる 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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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