深夜2時の警告:なぜサイトが悲鳴を上げているのか
デプロイの成功という気分を、サイトのヘッダーに表示されたPHPの警告(Warning)ほど台無しにするものはありません。それは通常、クライアントへの納品直後や定期アップデート中など、最悪のタイミングで発生します。警告は、500 Fatal Errorのようにサイトを完全にオフラインにするわけではありませんが、見た目が悪く、サーバーのファイル構造を露呈させ、コードがデータを正しく処理できていないことを示してしまいます。
多くの場合、debug.logには以下のような犯人が記録されています:
Warning: Invalid argument supplied for foreach() in /var/www/html/wp-content/themes/mytheme/functions.php on line 125
PHPは文字通りに動きます。foreachを使うとき、あなたはサーバーに「リストを順番に処理せよ」と命じています。もしそのリストがただの数字だったり、"false"(論理値)、あるいは単なる空のスペース(null)だった場合、PHPは処理を止めて不満を漏らします。WordPressのエコシステムでは、関数が目的のデータを見つけられなかった際に false や WP_Error を返すことが多いため、この現象が頻繁に発生します。
内訳:なぜループが失敗するのか
foreach ループには、array(配列)または object(オブジェクト)が必要です。それ以外はすべて警告を引き起こします。典型的なWordPress開発において、以下の4つの場所でこの問題に遭遇することになります:
- ACF(Advanced Custom Fields): まだ値が入っていないリピーター(Repeater)やギャラリー(Gallery)に対して
get_field()を使用した場合。 - カスタムデータベース操作:
$wpdb->get_results()を実行し、行セットではなくエラーオブジェクトを受け取った場合。 - REST APIの連携: 外部サービスからデータを取得する際、404エラーやタイムアウトが発生した場合。
- メタデータ:
get_post_metaを$singleパラメータを true にして呼び出し、期待される配列ではなく文字列が返ってきた場合。
的確な修正と防御策
アプローチ 1:クイックな「応急処置」的修正
すぐに警告を消す必要がありますか?型キャスト(Type casting)が最短ルートです。変数を強制的に配列形式にすることで、たとえリストが空であっても、ループが常に有効なリストを受け取るように保証します。
// functions.php の 125 行目
$items = get_post_meta( get_the_ID(), 'my_custom_list', true );
// 変数を配列として振る舞うように強制する
foreach ( (array) $items as $item ) {
echo esc_html( $item );
}
トレードオフ: これは本番環境での優れた一時的な絆創膏になります。しかし、根本原因を治さずに症状を隠しているだけです。もしデータベース接続の不備で $items が空になっている場合、デバッグに役立つはずのエラーを見逃すことになります。
アプローチ 2:「セーフティゲート」による防御(推奨)
現代的なPHP開発は、防御的プログラミング(defensive programming)に基づいています。データが存在すると決めつけず、使用する前に検証しましょう。PHP 7.1以降、is_iterable() を使って、配列とオブジェクトの両方を一度にチェックできるようになりました。
$gallery_images = get_field('property_gallery'); // 一般的な ACF ギャラリー
if ( is_iterable( $gallery_images ) ) {
foreach ( $gallery_images as $image ) {
echo '<img src="' . esc_url($image['url']) . '" alt="" />';
}
} else {
// データがないことをログに記録するか、代替のUIを表示する
error_log('Missing gallery for Post ID: ' . get_the_ID());
}
アプローチ 3:デフォルト値の設定
きれいなコードとは、多くの場合、関数が常に同じデータ型を返すことを意味します。チームメンバーのリストを返す関数であれば、メンバーが見つからない場合でも null ではなく空の配列を返すように徹底します。
function get_custom_team_members() {
$members = get_option('my_team_list');
// データが破損しているか存在しない場合は早期リターン
if ( ! is_array( $members ) ) {
return [];
}
return $members;
}
// 下記のループは100%安全です
$team = get_custom_team_members();
foreach ( $team as $member ) {
// メンバーが存在する場合のみ、ここが実行される
}
文脈による違い:ACF と $wpdb
Advanced Custom Fields は、このエラーの最大の発生源です。ユーザーがリピーターフィールドを作成しても中身を空のままにした場合、ACFは false を返します。テンプレート側でそのフィールドが配列であることを前提にしていると、サイトが壊れます。ACFのループは常に if( have_rows() ) や is_array() のチェックで包むようにしてください。
カスタムSQLについても、$wpdb クエリは同様に脆弱です。SQLの構文がわずかに間違っているだけで、$wpdb->get_results() は null を返す可能性があります。反復処理を行う前に、必ず !empty($results) であることを確認しましょう。
検証:修正が機能することの確認
- 空の状態を強制する: WordPressのエディタで問題のフィールドのデータを削除します。ページが警告なしでクリーンに読み込まれるはずです。
- ログを監視する: ターミナルを開き、
tail -f wp-content/debug.logを実行します。サイトを3〜4回更新して、新しいエントリが表示されないことを確認します。 - PHP 8 の厳格さをチェックする: 最近 PHP 8.0 や 8.1 にアップグレードした場合は、エンジンがより厳格になっていることを忘れないでください。これらの防御的なチェックは、もはや単なる「優れた習慣」ではなく、安定性のために「必須」となっています。
最後に
「うまくいくはずだ」という希望に頼ったコードを書くのはやめましょう。外部APIは失敗し、ユーザーは入力を飛ばし、データベースはしゃっくりを起こします。is_iterable() や型キャストを使用することで、たとえデータが見つからない場合でも、沈黙を守りプロフェッショナルであり続けるテーマを構築できるのです。

