始める前に
- 報告には自分が管理する対象URLと操作記録。ログ調査には匿名化して扱えるエラー記録
- 入力の違いを再現する担当者には、本番への実送信をしない隔離テスト環境
- 発生時刻と操作の記録。例外の種類を調べる担当者にはログを確認できる権限
用語が分からないとき
- 例外
- 処理の途中で問題が起きたことを知らせるエラーです。名前、メッセージ、発生した行と入力条件を一緒に見ます。
- 呼び出し履歴
- エラーに至るまで、どの処理から次の処理が呼ばれたかを示す記録です。
- 照合
- 二つの記録を見比べ、同じ対象・時刻・内容かを確かめることです。
不具合を報告する運営担当の方へ
ログを見る権限がなくても、発生した操作と時刻を揃えると実装担当が調べる範囲を絞れます。下のPHP演習や入力を省いたリクエストの再現は、隔離環境で行う担当者の作業です。
設定を変えずに確認すること
- 失敗した時刻・タイムゾーン、対象URL、操作、画面の文言を控える。問い合わせなら任意のURL欄を空欄にしたか、送信待ちか、成功・失敗の表示が出たかを分ける。
- 確認できた影響を残す。一つのフォームで起きたことだけを、サイト全体の停止と扱わない。調査のために本番の送信を繰り返さず、追加の再現は担当者と相談する。
この手順で使う用語
- 例外
- 処理を続けられなかったときのエラー情報。種類と発生位置は担当者がログで確認します。
- 呼び出し履歴
- 処理がどの経路で呼ばれたかの記録。先頭の行が必ず修正する自分のコードとは限りません。
- キーなしと空欄
- 項目がリクエストに含まれない状態と、項目が空の値で含まれる状態。画面を空欄にしただけでは両方を試したことになりません。
実装担当へ渡す情報
発生時刻、URL、操作、任意欄の状態、期待した画面と実際の文言、直前に変えたことを渡します。ログの所在を知る担当へ、同じ公開版・同じ時刻の記録を確認してもらってください。
確認済みと未確認の境界
ログ未閲覧なら原因は未確認です。実装担当が隔離環境で直した後も、入力処理の回帰確認と実受信は別の結果として残します。
本番を再操作する前に、再現条件を控える
本番フォームを連打せず、失敗した時刻、URL、任意項目を送ったか、期待していた表示を控えます。管理者はその時刻のログから、例外名と自分のアプリに関係するファイル・行を探します。認証情報や問い合わせ本文は公開のメモへ貼らないでください。
ここで扱うのはLaravelの任意項目の例です。他のアプリの例外を同じ原因と決めつける手順ではありません。配列のキーがない場合、空文字の場合、URLの値がある場合を、メール配送を差し替えた隔離テストで別々に再現します。
最初に、影響と再現条件を短く書く
この障害では問い合わせ処理が対象URLの値を取り出す段階で失敗していました。必須項目が揃った送信でも、target_urlというキーを送らないクライアントでは落ちる条件です。URL欄を空欄にした普通のブラウザ送信だけでは、キー自体を省いた送信と同じ条件になるとは限りません。
調査メモには「問い合わせ送信」「target_urlのキーなし」「Undefined array key target_url」の三つを先に書きます。時刻と直前の変更も添え、他のページまで落ちているかを確認します。これで影響範囲を一つのフォームに絞り、長いトレースの中でも値を取り出す場所を探せます。
nullableと未定義キー:原因の確認と修正例
Laravelではtarget_urlに文字列の検証とnullable(nullの値を許すルール)を指定していました。しかし入力にキーがない場合、検証済みデータにそのキーが存在する前提にはできません。修正前はメール本文の組み立てで直接アクセスしていたため、値の有無を調べる前に未定義キーの問題が起きました。
修正では??でキーがない場合を扱ってから、未記入の表示を決めました。この書き方は「値が空でもよい」と「キーがなくてもよい」を区別します。例では空文字列を未記入扱いにしていますが、0などを有効値とする別の項目では、?:の扱いをそのまま流用せず仕様を決めてください。
Undefined array keyの原因に合わせて直す
存在しない配列キーを直接読み取ったときの問題です。任意項目か必須項目かで修正方法が変わります。
例外に出たキーと、参照している行を調べる
ログの例外名、キー名、自分のアプリのファイル・行番号を照合します。フォームのname、送信データのキー、検証ルール、参照側の綴りが一致しているか確認します。
任意ならキーがない場合の値を決める
この例のtarget_urlは任意項目です。??でキーがない場合またはnullの場合を扱い、その後で未記入表示を組み立てます。nullableを付けるだけでは、キー自体が必ず存在するようにはなりません。
必須なら入力検証で不足を返す
必須データの欠落や綴り間違いを、すべて??で隠さないでください。必須項目ならrequired等の検証、生成側の誤りなら生成処理を直します。キーなし・null・空文字・値ありを配送なしのテストで確認します。
nullを有効な値として、キーがない場合と区別する必要がある項目ではarray_key_exists等で存在を判定します。下の修正例は、未入力のURLを未記入と表示するためのものです。
手順の参照:Laravel 13:入力検証と任意項目(別タブ)。に資料を確認しました。
// 修正前:キーがなければ直接参照で問題になる
'対象URL: '.($validated['target_url'] ?: '(未記入)');
// 修正後:キーがない場合を先に扱う
'対象URL: '.(($validated['target_url'] ?? '') ?: '(未記入)');
任意項目で分ける三つの入力
入力条件を分けるための例です。本番メールを使わずにテスト環境で試します。
| 条件 | 送信データの例 | 確認すること |
|---|---|---|
| キーなし | target_urlを含めない | 未定義キーで落ちない |
| 空欄 | target_urlが空文字列 | 未記入の表示になる |
| 値あり | target_urlに記事URL | 検証された値で処理される |
本物の宛先へ送らず、入力の違いを試す
下の説明用コードをローカルの新しいexercise.phpへ保存し、php exercise.phpで実行できます。キーなしだけ修正前が失敗し、修正後は未記入になります。空欄は前後とも未記入、値ありは前後とも同じURLです。PHPの警告を例外へ変換する最小演習で、Laravelの入力検証、実メール、フォーム全体のテストではありません。本番や公開ディレクトリには置かないでください。
再現では、必須項目の名前・返信先・本文をテスト用の値に固定し、対象URLだけを変えます。キーなし、空文字列、文字列ありを別ケースにしてください。LaravelのMail::fakeを使えば配送を差し替えられ、調査中に問い合わせメールを増やさずに処理を確認できます。
evulixにはtarget_urlを省いた送信の回帰テストがあり、今回の確認でも成功しました。テストで落ちなくなったことと、実際のSMTPから受信できることは別です。入力処理はテストで確認し、メールの到達は本番で許可されたテスト送信と受信側の確認で確かめます。
<?php
// ローカルのPHP CLIで実行する説明用の演習。通信もメール送信も行わない。
// 未定義キーの警告を例外に変換し、Laravelでの失敗条件を最小化する。
set_error_handler(static function ($level, $message, $file, $line) {
throw new ErrorException($message, 0, $level, $file, $line);
});
$cases = [
"キーなし" => [],
"空欄" => ["target_url" => ""],
"値あり" => ["target_url" => "https://example.invalid/article"],
];
foreach ($cases as $label => $values) {
try {
$before = $values["target_url"] ?: "(未記入)";
} catch (ErrorException $error) {
$before = "未定義キーで失敗";
}
$after = ($values["target_url"] ?? "") ?: "(未記入)";
echo $label." | 修正前: ".$before." | 修正後: ".$after.PHP_EOL;
}
restore_error_handler();
検索に使うログと、保管するログを分ける
エラーを調べるために外部へ渡すのは、例外名、秘密を含まない短いメッセージ、関連する処理の最小例に絞ります。ログにはURLのクエリ、メールアドレス、認証情報が入ることがあります。本文やトレースを一括でコピーする前に、何が含まれているかを確認してください。
復旧後は同じ再現条件で成功するか、別の例外が出ていないかを見ます。記録は「原因」「変更箇所」「確かめた入力」「未確認の範囲」を残すと次に使えます。この事例なら入力キーの扱いは修正済みでも、メールの配信遅延や迷惑メールへの振り分けは別に調べる事項として残します。
直らない場合は、例外と入力条件を取り直す
キー参照を直しても失敗するなら、例外が同じかを確認します。入力検証のエラー、SMTPの失敗、ブラウザ側の表示不具合は別の段階です。同じ「送れない」という報告でも、修正した箇所と新しい失敗が一致するとは限りません。
修正の記録には、変更前の再現条件、原因、変更点、キーなし・空欄・値ありの結果を揃えます。画面の成功は実受信とは分け、受信の確認は別の許可済みテストで行います。例外が変わった場合は未解決事項として残し、成功と記録しません。
長い履歴から、自分の処理を選んで読む
ログでは、例外名とメッセージ、発生したファイル・行、呼び出し履歴を分けて読みます。例外が発生した行と、その処理を呼び出した行は別です。履歴の先頭だけで原因を決めず、同じ公開版のソースと送信した入力へ戻って確認します。
2026年10月8日、現在のLaravelのエラーハンドラと説明用の小さな処理を使って、未定義キーを再現しました。履歴の#0はLaravelの警告処理、発生位置はContactBody.phpの9行目、#2はそれを呼んだContactAction.phpの9行目でした。今回は自分の入力値の扱いを直す例であり、vendorの一行目を編集する例ではありません。依存ライブラリに本当の問題がある場合まで否定するものではありません。
演習では.envや本番設定を読み込まず、入力検証・Web画面・メール配送を起動していません。過去の本番ログを復元したものでもありません。実際の障害では同じ時刻と版の記録を使い、必要な箇所だけ匿名化して担当者へ渡します。
同じ障害の記録を一つ選ぶ
操作・発生時刻・対象・公開版を揃えます。似たメッセージの別時刻の記録を混ぜず、結果が返らなかった場合も本番で連打して再現しません。
メッセージと発生位置を読む
例外名、短いメッセージ、ファイル・行を控えます。行番号だけを追わず、その版のソースで何を参照したか、どの値が渡る前提かを確認します。
呼び出し元から入力条件を追う
履歴の自分の処理とフレームワークの処理を見分けます。呼び出した行は原因候補を絞る手掛かりです。キーなし・空欄・値ありを、通信しない環境で比べます。
修正結果と未確認を担当へ渡す
同じ条件の修正前後と、例外が変わったかを記録します。元の症状が消えても入力検証やメール到達は別確認です。次の作業、担当、再確認の条件を残します。
例外の発生位置と呼び出し元を分ける
| 記録の部分 | この演習での意味 | 次に確認すること |
|---|---|---|
| ErrorException / Undefined array key "target_url" | キーの参照で警告が発生し、Laravelのハンドラが例外へ変換した。 | 同じ時刻・同じ操作の記録かを照合。すべてのフォーム障害をこの原因としない。 |
| 発生位置: ContactBody.php:9 | 今回、存在しないキーを参照した行。 | 同じ版のソースで、渡された配列と値の取り出し方を読む。 |
| #0 vendor/.../HandleExceptions.php:263 | 今回、警告を例外へ変換したフレームワークの処理。 | 先頭という理由だけでvendorを編集しない。依存側の問題の可能性は別の根拠で判断する。 |
| #2 ContactAction.php:9 → ContactBody::before | 値を参照する処理を呼んだ演習側の行。 | どの入力条件でここへ来たかを追う。この行自体が不具合とは限らない。 |
| 修正後の3条件 | キーなしは未記入へ変わり、空欄と値ありは同じ結果を保持。 | 確認した条件だけを成功と記録。HTTP・入力検証・受信は未確認に残す。 |
完了の確認
- 同じ版・時刻の例外の発生位置と呼び出し元を照合し、入力キーなし・空欄・値ありを区別する
- 通信を止めた同じ再現条件で修正前後を比べ、別の例外が残れば未解決として記録する
- 原因と候補・変更・実施結果を秘密を除いて残し、CLIや配送を止めたテストを実受信の証拠にしない
確認できなかった項目は、未確認として次の作業に残してください。