0
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】DBの変更不可(ロック)データと画面入力を安全に比較・フィルタリングする方法と、PHPDocで`mixed`迷子を防ぐ

0
Posted at

PHPで業務システムや管理画面を作っていると、**「すでに確定(公開・ロック)しているデータは勝手に変更させたくない」**という仕様によく遭遇します。

しかし、画面からの入力(文字列や空文字の混在)とDBの値(型が曖昧だったり0埋めされていたり)をただ比較しようとすると、型変換の罠("00" のバグ)にハマったり、エディタ上(VS CodeのIntelephenseなど)で配列の中身が mixed になってしまってコード補完が効かなくなるストレスに直面しがちです。

今回は、これらの課題をスマートに解決する確実なフィルタリング手法エディタの型推論(PHPDoc)の書き方をわかりやすくまとめました。


1. 実現したいこと(今回の仕様)

  • DB側の状態: 特定のカテゴリ(id)ごとに、すでに確定(ロック)されている「月(target_month)」のデータが存在する。
  • 画面からの入力: ユーザーが画面上のチェックボックスなどで変更を加えて送信してきた「月」の配列(空文字なども混ざる可能性がある)。
  • 目的: 画面から送られてきたデータの中から、「DB側ですでにロックされている月」だけを安全に抽出し、不正な変更を削ぎ落とした配列に作り直す。

2. 実装コード:安全に月をフィルタリングする

さっそく、仕様を満たすクリーンなコードを見てみましょう。
(※変数名は分かりやすいように、DBデータを $dbItems、画面入力を $postMonths に整理しています)

// 1. 事前準備: DBから取得した「変更不可(ロック)」の月をIDごとにリスト化
$lockedMonthsById = [];
foreach ($dbItems as $row) {
    // ※ エディタに型を教えるためのインライン型アノテーション(後述)
    /** @var array{id: int, target_month: int, name: string} $row */

    $id = $row['id'];
    // DBから取得した月を「数値キャスト ➔ 2桁の0埋め文字列」に統一(例: 4 ➔ "04")
    $lockedMonth = sprintf('%02d', (int)$row['target_month']);

    // IDをキーにした二次元配列に格納
    $lockedMonthsById[$id][] = $lockedMonth;
}

// 2. 画面入力のループ処理とフィルタリング
$filteredResults = [];
foreach ($targetIds as $index => $id) {
    // 画面から送られてきた該当IDの月配列を取得(なければ空配列)
    $months = $postMonths[$index] ?? [];
    $name = $postNames[$index] ?? '';

    // 【メイン処理】空文字を除外 + ロックされている月だけを生き残らせる
    $cleanMonths = array_values(array_filter($months, function ($month) use ($id, $lockedMonthsById) {
        // 条件①: 画面からの入力が空文字(未選択)なら即除外
        if ($month === '') {
            return false;
        }

        // 比較のために画面からの入力も「2桁の0埋め文字列」に揃える
        $formattedMonth = sprintf('%02d', (int)$month);
        
        // 現在処理しているIDに紐づく、ロックされた月のリストを取得
        $lockedMonths = $lockedMonthsById[$id] ?? [];

        // 条件②: ロックされている月リストの中に、今回の月が存在するかチェック
        // 存在していれば true(配列に残る)、なければ false(配列から消える)
        return in_array($formattedMonth, $lockedMonths);
    }));

    // 安全にフィルタリングされた配列を結果に格納
    $filteredResults[$id] = [
        'name' => $name,
        'months' => $cleanMonths,
    ];
}


3. コードのポイントを初心者向けに解説

array_filterarray_values の名コンビ

array_filter は、配列の要素を1つずつチェックし、中の関数(クロージャ)が true を返した要素だけを生き残らせる便利な関数です。

ただし、これを使うと生き残ったデータのインデックスが [0 => "04", 2 => "08"] のように歯抜け(連番が崩れる)になってしまいます。
最後に array_values() で包んであげることで、[0 => "04", 1 => "08"] のように綺麗に連番を振り直す
のがPHP配列操作のお約束です。

② 表記ブレを破壊する sprintf('%02d', (int)$value)

画面からくる値が "4"4、DBからくる値が "04" のように表記がブレていると、in_array などの比較が正しく行われません。
一度 (int) で強制的に数値にしたあと、sprintf('%02d', ...) を通すことで、双方が必ず "04""12" のような「2桁の文字列」に揃うため、安全に比較ができます。

⚠️「00」の罠に注意!

もし画面から "abc" などの不正なゴミデータが紛れ込んだ場合、(int)"abc"0 になり、sprintf によって "00" に変換されてしまいます。
もしDB側にバグ等で "00" という値が存在すると、「ゴミデータとDBの値が一致した」と判定されてチェックをすり抜ける原因になります。画面からの入力が数値である保証がない場合は、事前に ctype_digit() などで数値チェックを挟むとより堅牢になります。


4. エディタで配列のキーが mixed になる問題を解決する

PHPで配列(array)を使っているとき、関数の戻り値(@return)に細かく構造を書いても、呼び出し側の foreach の中で個別のキー($row['id'] など)を取り出した瞬間に、エディタ上で mixed(型不明) に戻ってしまうことがあります。

// 関数の戻り値で細かく書いても、ループを跨ぐとエディタが型を忘れてしまう…
/** @return array{id: int, target_month: int, name: string}[] */

解決策:インライン型アノテーションの活用

エディタ(VS CodeのIntelephenseなど)は、複雑な配列が複数のループを跨ぐと推論を諦めてしまいます。
そのため、データを取り出す・使う直前(foreachの直前や内部)で、「この変数の中身はこれだよ!」ともう一度教えてあげる(インライン型アノテーション)のが最も確実です。

foreach ($dbItems as $row) {
    // 💡 ループの先頭で、この変数の構造をエディタに明示する
    /** @var array{id: int, target_month: int, name: string} $row */
    
    // これで $row['id'] にカーソルを当てても mixed にならず int と認識される!
    $id = $row['id']; 
}

キー: 型 のコロンの後に半角スペースを入れる(id: int)のが、現在の多くのエディタで正しく認識させるコツです。

まとめ

  1. 画面入力の絞り込みには、array_filterarray_values のセットが最強。
  2. 文字列や数値の表記ブレは、sprintf('%02d', (int)$var) で2桁の文字列に統一して安全に比較する。
  3. エディタが配列の型を忘れて mixed 迷子になったら、foreach 内で / @var ... */ を書いてあげる。

これらを意識するだけで、バグが少なく、開発中のコード補完もサクサク効く快適なPHPコードが書けるようになります。ぜひ試してみてください!

0
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
0
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?