今月、弊サイトホスティング先でDBサーバーのメンテナンスがあるそうだ。深夜(日本時間)の数時間だし、DB接続エラーが見えたとしても問題ないが、せっかくなのでdb-error.phpを設置することにした。
私が認識しているおおまかなWPのローディングプロセスだが、まずルートのwp-config.phpが読まれ、wp-settings.php内でwp-includes配下の関数やクラス群が読み込まれ実行される。この中のclass-wpdb.phpでデータベースが初期化されグローバル変数$wpdb
がバインドされ、DBに接続される。そしてDBサーバーがダウンしている間はここでエラーが投げられ、デフォルトのエラー表示かdb-error.phpが設置してあれば後者が呼ばれ、PHPのexit
またはdie
が呼ばれてWPが終了する。つまり、サイトタイトルはもちろんテーマのファイルパスなど一切取得できていない状態である。
db-error.phpで表示しなければならないのはサイトタイトルとコンテンツが表示できないことの明示であろう。また、通常エラー表示では500エラー(500 Internal Server Error)がヘッダーステイタスとして返されるが、db-error.phpが設置された場合、コンテンツボディを表示するだけでは200 OKとなってしまう。検索クローラーなどに内容を拾われる可能性もあるので、500番台を返さなければならない。エラーではあるけどサーバーメンテナンスによるものなので、503(503 Service Unavailable)を返すことにする。
/wp-content/db-error.php
<?php
header ( $_SERVER [ 'SERVER_PROTOCOL' ] . ' 503 Service Unavailable' , true );
header ( 'Content-Type: text/html; charset=utf-8' );
header ( 'Cache-Control: no-store' );
?>
<!DOCTYPE html>
< html lang = en >
< head >
< meta charset = utf-8 >
< title > DB error | Study more, reveal more</ title >
...
メンテナンス時以外のエラーも発生する可能性もあるので、ログも記録しておこう。
/wp-content/db-error.php
...
</body>
</html>
<?php
$last_err = error_get_last ();
$err = implode (
', ' ,
array_map (
fn ( $k ) => "[ { $k } ] => { $last_err [ $k ] } " ,
array_keys ( $last_err )
)
);
date_default_timezone_set ( 'Asia/Tokyo' );
$date = date ( DATE_ATOM , $_SERVER [ 'REQUEST_TIME' ]);
$log_file = WP_CONTENT_DIR . '/logs/db-error.sqlite' ;
if ( ! file_exists ( $log_file )) {
$pdo = new PDO ( 'sqlite:' . $log_file );
$pdo -> exec ( 'CREATE TABLE db_error_log (datetime DATETIME, req_method TEXT, req_uri TEXT, protocol TEXT, error TEXT);' );
}
if ( file_exists ( $log_file )) {
$dbh = new PDO ( 'sqlite:' . $log_file );
$dbh -> setAttribute ( PDO :: ATTR_ERRMODE , PDO :: ERRMODE_SILENT );
$stmt = $dbh -> prepare ( 'INSERT INTO db_error_log VALUES (:datetime, :req_method, :req_uri, :protocol, :error)' );
$stmt -> execute ([
'datetime' => $date ,
'req_method' => $_SERVER [ 'REQUEST_METHOD' ],
'req_uri' => $_SERVER [ 'REQUEST_URI' ],
'protocol' => $_SERVER [ 'SERVER_PROTOCOL' ],
'error' => $err ,
]);
}
最後に、db-error.phpとlogs/db-error.sqliteが直接アクセスされないように.htaccessを追加。