Transientをもりもり使用したテーマを構築した際、問題になるのは「変わらないんだけどキャッシュなの?バグなの?」というお客様からの質問。
お客様もわかりづらいし、私たちも調査などなどで時間を使ってしまいます。
管理者としてログインしているときにはフロントのTransientを効かせないようにすればいいんじゃないか?ということで、そんなことができるのかとコアファイルを覗いてみました。
[追記:2018/06/19]プラグインが公式プラグインディレクトリに掲載
ダッシュボードからボタン一つで全てのTransientがクリアできるプラグイン「Clear Transient From Dashboard」が公式ディレクトリに掲載されました。
get_transient()では返り値を一括制御できない
Transients APIについてはこちらを参照。
get_transient()で既にキャッシュされている値を取得、有効期限を確認して、期限が切れていたら値を作り直してset_transient()、切れていなかったら取得したキャッシュの内容を使用する…というのが基本の使い方。
Transientを効かせないというのは、「常にget_transient()がfalseを返せばいい」ということ。
それならと、うまいフィルターがないかどうか確認。
※wp-includes/option.php (WordPress 4.9.6)
/** * Get the value of a transient. * * If the transient does not exist, does not have a value, or has expired, * then the return value will be false. * * @since 2.8.0 * * @param string $transient Transient name. Expected to not be SQL-escaped. * @return mixed Value of transient. */ function get_transient( $transient ) { /** * Filters the value of an existing transient. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * Passing a truthy value to the filter will effectively short-circuit retrieval * of the transient, returning the passed value instead. * * @since 2.8.0 * @since 4.4.0 The `$transient` parameter was added * * @param mixed $pre_transient The default value to return if the transient does not exist. * Any value other than false will short-circuit the retrieval * of the transient, and return the returned value. * @param string $transient Transient name. */ $pre = apply_filters( "pre_transient_{$transient}", false, $transient ); if ( false !== $pre ) return $pre; if ( wp_using_ext_object_cache() ) { $value = wp_cache_get( $transient, 'transient' ); } else { $transient_option = '_transient_' . $transient; if ( ! wp_installing() ) { // If option is not in alloptions, it is not autoloaded and thus has a timeout $alloptions = wp_load_alloptions(); if ( !isset( $alloptions[$transient_option] ) ) { $transient_timeout = '_transient_timeout_' . $transient; $timeout = get_option( $transient_timeout ); if ( false !== $timeout && $timeout < time() ) { delete_option( $transient_option ); delete_option( $transient_timeout ); $value = false; } } } if ( ! isset( $value ) ) $value = get_option( $transient_option ); } /** * Filters an existing transient's value. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 2.8.0 * @since 4.4.0 The `$transient` parameter was added * * @param mixed $value Value of transient. * @param string $transient Transient name. */ return apply_filters( "transient_{$transient}", $value, $transient ); }
フィルターはいくつかあるけど、全てフィルター名に$transientを使っている…。
$transientにはTransientの名前が格納されているので、Transientの名前ごとにフックをかけていかなければならない…フィルターを使用して一括で返り値を変更するのは難しそう。
オブジェクトキャッシュも一枚噛んでる
また、wp_using_ext_object_cache()の分岐も注目。オブジェクトキャッシュも絡んできているよう。
wp_cache_get()の中を少し遡ってみたけど、フィルターはなさそうだったので、ここでfalseが返せないとオブジェクトキャッシュ使用時はアウトですね。
※wp-includes/load.php (WordPress 4.9.6)
/** * Toggle `$_wp_using_ext_object_cache` on and off without directly * touching global. * * @since 3.7.0 * * @global bool $_wp_using_ext_object_cache * * @param bool $using Whether external object cache is being used. * @return bool The current 'using' setting. */ function wp_using_ext_object_cache( $using = null ) { global $_wp_using_ext_object_cache; $current_using = $_wp_using_ext_object_cache; if ( null !== $using ) $_wp_using_ext_object_cache = $using; return $current_using; }
うーん…これはどっかのタイミングで$_wp_using_ext_object_cacheをfalseに差し替えれば大丈夫そうかな?
get_option()でもどうにもできない
get_transient()内では返り値をfalseにはできなさそうだった…ということで、最後の砦、get_option()のフックを探してみる。
get_transient()内で、$transient_option = ‘_transient_’ . $transient; からの $value = get_option( $transient_option ); なので、get_option()に渡るときには、Transient名は全て ‘_transient_’ から始まるようになっている(DBを覗いたことがある人は、このコードを見なくても既に知っているかも)。
私の大嫌いな正規表現で「オプション名が’_transient_’ から始まったら false を返すよ!」ってしてあげればいいのでしょう。
…ということで、get_option()を覗いてみたのですが、あったフィルターはこちら!
※wp-includes/option.php (WordPress 4.9.6)
function get_option( $option, $default = false ) { ・・・省略・・・ $pre = apply_filters( "pre_option_{$option}", false, $option, $default ); ・・・省略・・・ return apply_filters( "default_option_{$option}", $default, $option, $passed_default ); ・・・省略・・・ return apply_filters( "option_{$option}", maybe_unserialize( $value ), $option ); }
もれなく$option!
SQL文で値を取得して、それをreturnしているので、これ以上遡ることは不可。なんということでしょう。
残念な結論
結論:一括なんてするな!Transient名ごとにフックしろ!
add_filter()で正規表現使えたり、フック増えたりすると嬉しいのだけど…ここまで拘っているということは、一括フィルターは用意する気なんてないよってことなんでしょうね。
おとなしくTransient名ごとにフックを書いてあげるしかないみたいです。