概要
フロントページに置いている、データベースクエリをたくさん発行するブロックをキャッシュする。
背景
フロントページに、新しい公開リストのタイトルと抜粋を6つ表示しています。これは WordPress 標準の「最新の投稿」ブロックを使っています。
新しい公開リストを投稿したときにのみ更新されればよい情報なので、キャッシュしてみようと思います。
内容
キャッシュには Transient API を使用
WordPress が標準で提供している永続的なキャッシュシステムである Transient API を使います。
キャッシュからの取得
add_filter( 'pre_render_block', 'lf_pre_render_block', 10, 2 );
function lf_pre_render_block( $pre_render, $parsed_block ) {
$post = get_post();
$post_id = $post->ID ?? 0;
if ( $post_id == get_option( 'page_on_front')
&& 'core/latest-posts' == $parsed_block['blockName'] ) {
$slug = &$parsed_block['attrs']['categories'][0]['slug'] ?? '';
$transient = '';
// 今日のリスト
if ( 'lotd' == $slug ) {
if ( $transient = get_transient( 'LF_BLOCK_LATESTPOSTS_LOTD' ) ) {
$pre_render = $transient;
}
// 新着リスト
} elseif ( 'list' == $slug ) {
if ( $transient = get_transient( 'LF_BLOCK_LATESTPOSTS_LIST' ) ) {
$pre_render = $transient;
}
}
// キャッシュから取得できなかったら
if ( ! $transient ) {
// ブロックをキャッシュする
add_filter( 'render_block_core/latest-posts', 'lf_render_block_latest_posts', 10, 2 );
}
}
return $pre_render;
}
- L1: ‘pre_render_block’ フックは、フックした関数が返す文字列をそのまま出力します。
- L4-5: 表示しようとしている投稿を知るための処理。L5 は 404 ページなど $post が作成されないケースへの対応。
- L7: フロントページかつ「最新の投稿」ブロックの場合のみキャッシュからの取得を試みます。
- L10: 読みやすさのためと、もしキーが存在していなくてもエラーにならないようにするための処理。
- L30: キャッシュから取得できなければ、ブロック書き出し後に発火するフィルターフック “render_block_{$this->name}” にコールバック関数を追加します。’render_block_core/latest-posts’ とすれば「最新の投稿」ブロックををレンダリングした直後にのみ起動します。
キャッシュへの保存
ブロックに表示するデータが作られた直後に実行されるフィルターフックに、次のような処理を挟みました。
function lf_render_block_latest_posts( $block_content, $parsed_block ) {
// For readability and safety
$slug = &$parsed_block['attrs']['categories'][0]['slug'] ?? '';
// 今日のリスト
if ( 'lotd' == $slug ) {
set_transient( 'LF_BLOCK_LATESTPOSTS_LOTD', $block_content, WEEK_IN_SECONDS );
// 新着リスト
} elseif ( 'list' == $slug ) {
set_transient( 'LF_BLOCK_LATESTPOSTS_LIST', $block_content, WEEK_IN_SECONDS );
}
return $block_content;
}
- L7,11: フロントページでは「今日のリスト」カテゴリの投稿についても「最新の投稿」ブロックを使っているので、両者を識別しないといけません。そこでパースされたブロック情報を調べて、カテゴリスラッグで識別することにしました。
- L8,12: 第三引数を外せば transient の寿命が無限になるうえにautoload(メモリ上に保存)されます。しかし autoload するような内容でもないので、公開リストの投稿間隔の見積もり以上に長い期間を見込んで寿命を設定しておきました。
キャッシュの更新(削除)
「今日のリスト」を入れ替える、リスト投稿が公開されるなど、ブロックの内容を書き換えるべきイベントによって発火するフィルターフックに、Transient の削除処理を登録します。以下は新着リストブロックを書き換える例。
add_action( 'transition_post_status', 'lf_post_status', 20, 3 );
function lf_post_status( $new_status, $old_status, $post ) {
// (投稿タイプ、カテゴリ、投稿ステータスなどで絞り込む)
delete_transient( 'LF_BLOCK_LATESTPOSTS_LIST' );
}
この方法を用いれば、どんなにデータベースクエリの多いブロックでも、2回のクエリ( transient とその寿命情報)で済むようになります。
複数ブロック、あるいは the_content() の出力をまとめてキャッシュできないか?
簡単な方法が見つかりませんでした。the_content() は次のようにしてブロックを表示しています。
- テンプレートファイル
- the_content(): テンプレートファイル中で呼び出している本文表示関数
- ‘the_content’: 上記関数内のフィルターフック
- do_blocks(): /wp-includes/default-filters.php で上記にフックされている関数
- render_block(): 上記関数内で呼ばれている関数
- do_blocks(): /wp-includes/default-filters.php で上記にフックされている関数
- ‘the_content’: 上記関数内のフィルターフック
- the_content(): テンプレートファイル中で呼び出している本文表示関数
do_blocks() 内の冒頭&最後にフィルターフックがあればブロックエディタの出力をまるごとキャッシュできそうですが、存在せず。
the_contents() も同様です。この関数はすべての処理を ‘the_content’ フックに登録したフィルターを使って実施しています。デフォルトのフィルターは default-filters.php にあります。
すべてのフィルターの前に、つまり最高の優先順位でキャッシュ取得フィルターをフックしておき、もしキャッシュが取得できたら以降のフックを解除してしまう、といった操作を行えばできそうですが、そこまでするのは面倒なので断念。