1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PHPで連想配列のキーに'52000'を使うとint化されて === 比較が永遠にfalseになる罠

1
Posted at

Claude Codeをに実装をお任せした時にハマったことです。

結論(先に解決策)

PHP は**「整数として正規な10進数文字列」を配列キーに使うと、自動的に int に変換**します。

$map = ['52000' => '名古屋圏'];
var_dump(array_keys($map));   // int(52000) ← string ではない!

そのため、後で === '52000'(文字列)で厳密比較すると永遠に false になり、foreach でのマッチが全部外れます。

対処はキーを使わないリスト形式に変えるのが一番安全です。

// NG: キーが int 化される
$map = ['52000' => '名古屋圏', '53000' => '大阪圏'];

// OK: list-of-arrays。キーを持たないので型が壊れない
$map = [
    ['code' => '52000', 'label' => '名古屋圏'],
    ['code' => '53000', 'label' => '大阪圏'],
];
foreach ($map as $item) {
    if ($item['code'] === '52000') { /* マッチする */ }
}

症状

参照テーブルが「空っぽ」になる、特定コードのラベルだけ出ない、foreach でヒットしないのに目視ではキーが存在している——といった**「あるはずなのにマッチしない」**現象。

$regions = ['52000' => '名古屋圏', '53000' => '大阪圏'];

$code = '52000'; // API などから来る5桁の都市圏コード(文字列)

$label = null;
foreach ($regions as $key => $name) {
    if ($key === $code) {   // ← ここが絶対 true にならない
        $label = $name;
    }
}
var_dump($label); // NULL(名古屋圏が取れない)

isset($regions[$code]) 自体は通ることがあるので余計に混乱します(後述)。


原因

PHP マニュアルの仕様です。配列キーは int か string のどちらかしか取れず、次のキャストが行われます。

整数として妥当な値を含む文字列は、(先頭にゼロが付くなどの例外を除き)int にキャストされる。

つまり:

書いたキー 実際に格納される型
'52000' int(52000)
'01000'(先頭ゼロ) string('01000')(int として非正規なので文字列のまま)
'52000abc' string(数値として非正規)
' 52000'(空白) string

'52000' は「正規な整数文字列」なので int 化されます。
そこへ === '52000'(string)をぶつけると、int(52000) === string('52000') は型が違うので false。これが「永遠にマッチしない」の正体です。

var_dump(52000 === '52000');  // bool(false) … 厳密比較は型も見る
var_dump(52000 == '52000');   // bool(true)  … 緩い比較なら通る(が型は揃わない)

なぜ isset だと気づきにくいか

isset($arr['52000'])添字アクセスでも同じ int 化が起きるため、isset は true を返します。「キーは存在するのに === で比較すると無い」というねじれた状態になり、原因にたどり着きにくいのです。

$regions = ['52000' => '名古屋圏'];
var_dump(isset($regions['52000']));   // bool(true)  … 添字も int 化されるため一致
// でも array_keys で取り出した key は int なので、=== '52000' は false

解決

方法1: キーを持たないリスト形式にする(推奨)

数値文字列をとして持てば、型は一切壊れません。前述の list-of-arrays が確実です。

$regions = [
    ['code' => '52000', 'label' => '名古屋圏'],
    ['code' => '53000', 'label' => '大阪圏'],
];

function region_label(array $regions, string $code): ?string {
    foreach ($regions as $r) {
        if ($r['code'] === $code) {   // string 同士で確実にマッチ
            return $r['label'];
        }
    }
    return null;
}

方法2: どうしてもキーで引くなら型を揃える

連想配列のまま使いたい場合は、比較側も int にそろえるか、緩い比較に統一します。ただし「コードは文字列で扱う」という前提を崩すので、プロジェクト全体で一貫させてください。

// 添字アクセスなら int 化が両側で起きるので一致する
$label = $regions[$code] ?? null;      // OK($code が '52000' でも int 52000 として引かれる)

// foreach で key を使うなら (string) で戻すか == にする
foreach ($regions as $key => $name) {
    if ((string)$key === $code) { /* ... */ }
}

ただし '01000' のような先頭ゼロのコードは int 化されないため、コード体系に5桁ゼロ埋め(都道府県コード等)が混在する場合は型が揺れます。**「コードは常に文字列、配列キーには使わない」**と決めてしまうのが事故が少ないです。


補足

  • 同種の罠に、json_decode($s, true) で得た配列でも数値文字列キーは int 化される、array_merge で数値キーが振り直される、などがあります。**「PHP の配列キーは int か string、数値文字列は int に倒れる」**という一点を覚えておけば全部説明できます。
  • ゼロ埋めコード('01000', '13100' など行政コード)を扱うときは、キーにせず値として持ち、比較は必ず === で文字列同士にそろえるのが安全です。

Claude Codeで開発したサイト

人口カルテ47

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?