php pitfalls

生成したPHPで踏んだ5つの罠|UTC・古いコードが動く・関数が未定義・500

AIに書かせたPHPは、手元の確認では動き、本番で静かに間違うことがありました。多くは「エラーが出ない」か「出ても画面に見えない」形です。この面は、実際に踏んだ5つを、症状・原因・確かめ方・直し方の順で残した記録です。サーバー事業者に依存しない書き方にしてあります。

このサイトはAnthropicの公式サイトではありません。Claude Codeの機能・料金・提供範囲は変わる可能性があるため、重要な判断の前には公式のドキュメントと自分のアカウント設定を確認してください。

先に結論:5つの罠と最初の確認

罠症状最初の確認
時刻がUTC申込の受信日時が9時間ずれるdate('c') を出して末尾のオフセットを見る
古いコードが動き続ける上書きしても出力が1バイトも変わらない別のファイル名で置いて、そちらが新しいか見る
条件内の関数保存で500。なのに途中まで保存されている関数の定義行と呼び出し行の番号を比べる
//コメントの複数行ファイル全体が500。タグの数は合っている送信後に必ずHTTPで開いて200と長さを見る
cronのPHPがCGIログは毎日更新されるのに処理が0件ログの中身(処理件数)を読む

5つに共通するのは、「送信が成功した」「ファイルが一致した」が「動いた」の証拠にならないことです。必ず本番のURLを開いて、中身を読んでください。

読者が確かめる手順

  1. 時刻:フォームの日時は gmdate("Y-m-d H:i:s", time() + 9 * 3600) で書く。gmdate は時刻帯の設定を見ないので、サーバーが移っても壊れません。素の date() は設定任せです。
  2. 反映:既存のPHPを上書きして変わらなければ、別名でアップロードして開く。別名が新しい内容なら、古いバイトコードが残っています。別名で確認してから本番名へ上書きし、別名は必ず消します。メール送信など副作用のあるファイルは開かず、構文だけ確認します。
  3. 関数:PHPが先に読み込むのは、条件なしでトップレベルに宣言した関数だけです。if (!function_exists(...)) { function ... } の中は、その行を実行して初めて定義されます。定義はファイルの先頭付近に置きます。
  4. コメント:// の注記は1行に収める。長いなら /* */ にする。
  5. cron:cronのPHPがCGI版のことがあります。コマンドライン判定に PHP_SAPI !== 'cli' を使うと毎回止まります。isset($_SERVER['REQUEST_METHOD']) で判定し、Webからの実行はディレクトリの設定で拒否します。
  6. パス:__DIR__ . '/../x.php' のような相対の読み込みは、階層が変わると外へ出ます。複数の候補を順に探し、見つからなければ機能だけ出さないようにします。

手元にphpコマンドがないときの構文確認は、サーバーのPHPに「実行せず解析だけ」させます。token_get_all($src, TOKEN_PARSE) を使い、ParseErrorなら行番号を返す一時ページを作ります。直したファイルを公開の場所に置かないよう、内容はPOSTで送ります。

実際に起きた記録

1. 時刻がUTC(2026年9月20日)

あるサーバーのPHPは date.timezone がUTCでした。別のサーバーはJSTでした。約90サイトと52サイトに割れていたため、中央の申込一覧では両方の時刻が混ざりました。テストメールの本文は23:32なのに、届いたのは翌朝の08:32でした。深夜の申込に見えて、実は朝です。訂正が2つあります。1つは、共通の転送ファイルを require する型では、自分のファイルをgmdateにしても直らないこと。もう1つは、同じ日に「MySQLもUTC」と報告が出ましたが、実測でNOW()は日本時間でした。ずれるのは素の date() だけでした。報告を受けたら、直す前に「いまの日本時間」を2か所で確かめます。

2. 古いコードが動いた(2026年8月14日)

FTPで上書きしても出力が変わらず、「反映されていない」と判定して正しい変更を6回巻き戻しました。「反映されなければ戻す」という自動巻き戻しが、成功を消し続けました。新規の名前なら即座に配信されるので、パスは正しく、バイトコードの保持が原因でした。

3. 条件内の関数(2026年8月22日)

管理画面の560行目で定義した関数を、247行目のハンドラから呼びました。保存は500で落ちましたが、直前のUPDATEは通っていたため、文章は保存され、経路の列だけがNULLで残りました。1つの欠陥が「500」と「ボタンが押せない」の2つの症状に見えました。同じファイルに注意書きを残していたのに、自分で踏みました。

4. //コメントで500(2026年9月24日)

説明を3行の // で書き、2行目以降がコードとして読まれました。管理画面が500になりました。タグの数も、送信後のmd5照合も通っていました。落とした後は原因を探す前に、退避から作り直して戻しました。

5. cronが空振り(2026年8月4日〜9月24日)

CLI判定に引っかかり、52回すべて「コマンドライン専用です」で終了しました。ログは更新されていたので正常に見え、ジャンルページが7週間止まりました。別の追加確認として、Webで動くPHPの版がドメインごとに違い(7.4と8.3)、8系の関数を7.4側で使うとFatalでした。構文検査も版を合わせます。

おまけ:トップページの階層(2026年8月22日)

下層で動いた雛形をトップに置いて、読み込みが外へ出て500になり、1〜2分トップを落としました。別名で検証してから本番名に置く手順は、上げたあとに本番を読む記録にもあります。

よくある質問

PHPの時刻を揃えるには、php.iniを変えるのが近道ですか。

複数のサイトが同居するサーバーでは、他の処理を巻き込むので避けました。呼ぶ側を gmdate に直すのが安全です。

送信後に何を確かめれば十分ですか。

本番のURLをHTTPで開き、200であることと本文の長さ、画面の要点を読むことです。md5の一致は動作の証拠にはなりません。

cronが動いているかはどう見ますか。

ログの更新日時ではなく、中身(処理件数・残り件数)を翌日に読みます。件数が出ない処理は、出すように直します。

次に読むページ