題の通りです。PHPではstring型の異なる値同士の緩やかな比較をした際、結果が true になる場合があるというやつです。
PHP で同値を比較する演算子には==と===の二種類があります。このうち===による比較を厳密な比較と言い、==による比較を緩やかな比較と呼びます。厳密な比較の場合、比較する値同士の型が一致していないならば問答無用で false が返ります。一方で緩やかな比較の場合、比較する値同士の型が一致していなくとも true が返る場合があります。例えば次の様になります。
// 厳密な比較 var_dump(1 === '1'); // bool(false) // 緩やかな比較 var_dump(1 == '1'); // bool(true)
この比較については次から引用した型の比較表が詳しいです。
PHP: PHP 型の比較表 – Manual
TRUE |
FALSE |
1 | 0 | -1 | “1” | “0” | “-1” | NULL |
array() | “php” | “” | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
TRUE |
TRUE |
FALSE |
TRUE |
FALSE |
TRUE |
TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
TRUE |
TRUE |
FALSE |
TRUE |
| 1 | TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
FALSE |
FALSE |
| 0 | FALSE |
TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
TRUE |
FALSE |
TRUE |
TRUE |
| -1 | TRUE |
FALSE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
| “1” | TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
FALSE |
FALSE |
| “0” | FALSE |
TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
FALSE |
| “-1” | TRUE |
FALSE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
NULL |
FALSE |
TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
TRUE |
TRUE |
FALSE |
TRUE |
| array() | FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
FALSE |
FALSE |
TRUE |
TRUE |
FALSE |
FALSE |
| “php” | TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
FALSE |
FALSE |
TRUE |
FALSE |
| “” | FALSE |
TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
PHP の緩やかな比較の型の異なる際の挙動はこれで十分です。しかしながら緩やかな比較は上記表にある型変換に加えて、文字列同士の比較に際に特筆すべき挙動を起こします。具体例は次です
Online PHP editor | output for s9ol0 ↓のデモです。
<?php
var_dump('1' == '01'); // bool(true)
var_dump('1' == '001'); // bool(true)
var_dump('1' == '1e0'); // bool(true)
var_dump('1' == ' 1 '); // PHP 8.0.0 以上ならばbool(true)。PHP8.0.0 未満ならば bool(false)
var_dump('1' == '1 '); // PHP 8.0.0 以上ならばbool(true)。PHP8.0.0 未満ならば bool(false)
var_dump('1' == ' 1'); // bool(true) どのバージョンでも true
var_dump('0' == '0e0'); // bool(true)
var_dump('0' == '0e10'); // bool(true)
var_dump('0' == '00'); // bool(true)
var_dump('0' == '0.0'); // bool(true)
var_dump('0' == '-0'); // bool(true)
var_dump('0' == '+0'); // bool(true)
var_dump('0' == ' 0 '); // PHP 8.0.0 以上ならばbool(true)。PHP8.0.0 未満ならば bool(false)
var_dump('0' == '0 '); // PHP 8.0.0 以上ならばbool(true)。PHP8.0.0 未満ならば bool(false)
var_dump('0' == ' 0'); // bool(true) どのバージョンでも true
var_dump('r' == ' r'); // bool(false)
var_dump('r' == ' r '); // bool(false)
var_dump('r' == 'r '); // bool(false)
要するに文字列が数字と解釈できる時、数字を元にした数値が等しいならば true となる挙動があります。更なる特徴として文字列の頭と尻尾に空白があってもなくても文字列でなく数字として解釈されて true が返っていきます。この空白についての処理は PHP のバージョンで違いがあり、後ろの空白ありの場合 PHP8 以上ならば true 、PHP8 未満ならば false となります。
上記例では==のみを使っていますが、これは緩やかな比較全般に現れるためin_arrayをはじめとした各種比較関数でも同様です。そんな感じで==はなかなか信用ならないやつです。基本的に===や関数の引数などで厳密な比較を使う様にするべきであり、異なる型同士の比較をしたい時はそれ用の関数を用意すべきでしょう。
<?php
// 異なる型同士の比較関数例
/**
* string にキャストできる色々を文字列として比較することで型の違いを許しつつ比較を行う
* PHP の引数の型は暗黙の型変換を起こすための型なので以下だけでOK
*
* @param string $a
* @param string $b
* @return bool
*/
function loose_cmp(string $a, string $b): bool
{
return $a === $b;
}
var_dump(loose_cmp(1, '1')); // bool(true)
var_dump(loose_cmp(1, 1)); // bool(true)
var_dump(loose_cmp(1, 1.0)); // bool(true)
// string にキャストできないと例外を吐くので実際に使うにはもっと拡張すべき
// var_dump(loose_cmp([], []));// Error
var_dump(loose_cmp('1', '01')); // bool(false)
var_dump(loose_cmp('1', '001')); // bool(false)
var_dump(loose_cmp('1', '1e0')); // bool(false)
var_dump(loose_cmp('1', ' 1 ')); // bool(false)
var_dump(loose_cmp('1', '1 ')); // bool(false)
var_dump(loose_cmp('1', ' 1')); // bool(false)
var_dump(loose_cmp('0', '0e0')); // bool(false)
var_dump(loose_cmp('0', '0e10')); // bool(false)
var_dump(loose_cmp('0', '00')); // bool(false)
var_dump(loose_cmp('0', '0.0')); // bool(false)
var_dump(loose_cmp('0', '-0')); // bool(false)
var_dump(loose_cmp('0', '+0')); // bool(false)
var_dump(loose_cmp('0', ' 0 ')); // bool(false)
var_dump(loose_cmp('0', '0 ')); // bool(false)
var_dump(loose_cmp('0', ' 0')); // bool(false)
var_dump(loose_cmp('r', ' r')); // bool(false)
var_dump(loose_cmp('r', ' r ')); // bool(false)
var_dump(loose_cmp('r', 'r ')); // bool(false)