WordPress プラグインロードに条件分岐を使い高速化する方法

WordPress は、プラグインをたくさん使用すると動作が遅くなります (^_^;)

これは、現在有効になっているプラグインを無条件で読み込むようになっていることが一つの要因です

要求されたページを表示するのに関係ないプラグインを読み込んでしまっているかもしれないってことです

プラグインロード条件設定の効果

  1. サイト表示の高速化
  2. モバイルとデスクトップのプラグイン使い分け

高速化の例として、Contact Form7 を使っている場合を考えてみます。

実際に Contact Form7 を使用したいのはお問い合わせページだけかもしれませんが、WordPressは、通常 Contact Form7 プラグインを常に読み込んでしまいます

このように、無条件にプラグインを読み込んでいると、1つのプラグインの読み込みで、仮に50msec かかるとすると20個のプラグインを使っていると1秒かかることとなります

これが、プラグインは使い過ぎると重くなる理由です

また、TinyMCE Advanced 等のようなプラグインは管理画面での記事作成時にしか必要ありませんので、通常のサイト表示時に読み込む事自体がナンセンスです

一つのプラグインを読み込まなくて済むということは、そのプラグインのPHPプログラムだけでなく、同時に関連するCSS,JS,翻訳ファイル等も読み込まないってことです

表示ページによって必要なプラグインは変わってくるので、それを選択する条件設定を出来るようにするのが、plugin load filter プラグインです

最大の特徴は、PHPの読み込みを減らせることですが、同時にCSS,JSの読み込みも減らせるのでキャッシュプログラムと併用しても効果があります

どういうことかといいますとキャッシュプログラムは生成したページのHTMLコードを静的ページのように出力しますが、そのキャッシュされたHTMLコードのhead部にはスタイルシートやスクリプトの読み込みが記述されています

プラグインの読み込みを最適化することは、そのスタイルシートやスクリプトを読み込む記述に反映して無駄な読み込みも止める機能なので、キャッシュと併用することでもより効果を得られると思います (^^)

更に、デスクトップかモバイルデバイスかによってプラグインをロードするかどうかも設定できますので、例えば ”トップへ戻る” 等の機能をプラグインで実装している場合なら、この機能はデスクトップ時のみ有効とする等の使い勝手に関するところでも利用可能です

 

インストール

2015/4/17 公式サイトに公開しました (^^)

https://wordpress.org/plugins/plugin-load-filter/

公開するにあたって、ユーザーインターフェースの見直しとカスタムポストタイプのサポートを追加しています

プラグインのインストール方法や使い方等は別途まとめましたので下記記事を参照して下さい

WordPress Plugin : Plugin Load Filter
WordPress で沢山のプラグインを使用している場合に Plagin Load Filter プラグインを使うと、ページタイプや投稿フォーマット、カスタムポストタイプ毎に有効化するプラグインを動的に選択することが出来ます。 プラグインの読み込みを減らし、ページ表示のレスポンスを高速化する効果があります (^^)
WordPress Plugin : Plugin Load Filter

 

プログラムの仕組み

このプラグインの仕組について少し紹介します

まずはプラグインのロード処理を調べてみます

プラグインのロードは wp-settings.php ファイルに記述されているのでその部分を抜粋して見てみます

// Load active plugins.
foreach ( wp_get_active_and_valid_plugins() as $plugin ) {
    wp_register_plugin_realpath( $plugin );
    include_once( $plugin );
}
unset( $plugin );

ここの wp_get_active_and_valid_plugins() で有効なプラグインのPHPファイルを取得して、そのPHPファイルを include_once で読み込んでいるだけです

※マルチサイトに関しては今回は対象外とします

wp_get_active_and_valid_plugins の処理
function wp_get_active_and_valid_plugins() {
    $plugins = array();
    $active_plugins = (array) get_option( 'active_plugins', array() );

    // Check for hacks file if the option is enabled
    if ( get_option( 'hack_file' ) && file_exists( ABSPATH . 'my-hacks.php' ) ) {
        _deprecated_file( 'my-hacks.php', '1.5' );
        array_unshift( $plugins, ABSPATH . 'my-hacks.php' );
    }

    if ( empty( $active_plugins ) || defined( 'WP_INSTALLING' ) )
        return $plugins;

    $network_plugins = is_multisite() ? wp_get_active_network_plugins() : false;

    foreach ( $active_plugins as $plugin ) {
        if ( ! validate_file( $plugin ) // $plugin must validate as file
            && '.php' == substr( $plugin, -4 ) // $plugin must end with '.php'
            && file_exists( WP_PLUGIN_DIR . '/' . $plugin ) // $plugin must exist
            // not already included as a network plugin
            && ( ! $network_plugins || ! in_array( WP_PLUGIN_DIR . '/' . $plugin, $network_plugins ) )
            )
        $plugins[] = WP_PLUGIN_DIR . '/' . $plugin;
    }
    return $plugins;
}

やっているのは get_option 関数で active_plugins というデータを読み取るだけです

つまり、現在有効なプラグインは、active_plugins に保存されているPHPファイル名を読み取っているだけなので、ここに条件分岐を加えて指定されたページの表示に関係ないプラグインの PHPファイル名をフィルタリングしてしまえば良いということです

とてもシンプルです (^^)

※JetPackやceltispackプラグインでは、複数のモジュールを集めたプラグインとなっていますので、jetpack_active_modules, celtispack_active_modules という設定値として有効モジュールが保存されているので、同様にこの設定値の読み取りをフィルターフックしてしまえば条件分岐により機能の読み込みを最適化することが出来ます (^^)

フィルタリング実装

フィルタリングするためのポイント

  • get_option 関数にフィルターフック
  • 他のプラグインより先に処理するために必須プラグインとして実行

オプションデータを取得する get_option は、pre_option_ というフィルターフックを掛けられるようになっています

従って、get_option の ‘active_plugins’ というデータ取得に、’pre_option_active_plugins’ というフィルタフックを使い取得データを操作出来るってことです

これらの処理をプラグインが読み込まれる前の必須プラグインとして作成すればOKです

後は、ia_home とか is_archive とか is_single とかの条件分岐で処理するだけです

と思っていましたが、やってみるとうまく行きません (^_^;)

プラグインを読み込んだ時点では、まだリクエストページのURLをパースして、その投稿データを取得する処理が行われていないために、条件分岐が使えません

条件分岐を使うには

サーバーリクエストが、どこで処理されているか調べると、query_requests()query_posts() を実行すれば良いようです

参考:クエリ概要 – WordPress Codex 日本語版

本来はプラグインをロードした後に行われるリクエストのパースや投稿データの取得の一部処理を先に行う必要があるということです

これ以外にも、投稿フォーマットに対応や、ユーザー情報の未確定時の取得等、ちょっとコードが必要でしたが、概ね条件分岐出来るようになりました (^^)

コードを見てもらったほうが早いですね

$plugin_load_filter = new Plugin_load_filter();

class Plugin_load_filter {
    
    public $plugins = '';       //全プラグインファイル
    public $editem = false;     //編集対象プラグイン 
    public $filter = array();   //フィルターテーブルデータ

    public function __construct() {

        $this->filter = get_option('plugin_load_filter');
            
        if(is_admin()) {
            require_once(ABSPATH . 'wp-admin/includes/plugin.php');
            $this->plugins = get_plugins();
            if ( empty( $this->plugins ) ) 
                return;

            $this->action_posts();

            //管理画面(設定メニュー)
            add_action('admin_menu', array(&$this, 'my_option_menu')); 
            add_action('admin_init', array(&$this, 'my_option_register'));
            //add_action('switch_theme', array($this, 'unlink_plugin_load_filter') );
        }
        else {
            //ロード条件判定処理をフィルターフックで実行する
            if(!empty($this->filter))
                add_filter('pre_option_active_plugins', array(&$this, 'load_filter_active_plugins'), 10, 1);
        }
    }

    //プラグインロード条件を判断して、ロードするプラグインPHPファイルをフィルタリング
    public function load_filter_active_plugins( $default = false) {
        if ( defined( 'WP_SETUP_CONFIG' ) )
            return false;

        if(is_admin())          //管理モードは条件判定しない
            return false;

        $option = 'active_plugins';
        if ( ! defined( 'WP_INSTALLING' ) ) {
            // prevent non-existent options from triggering multiple queries
            $notoptions = wp_cache_get( 'notoptions', 'options' );
            if ( isset( $notoptions[$option] ) )
                return apply_filters( 'default_option_' . $option, $default );

            $alloptions = wp_load_alloptions();
            if ( isset( $alloptions[$option] ) ) {
                $value = $alloptions[$option];
            } else {
                $value = wp_cache_get( $option, 'options' );

                if ( false === $value ) {
                    $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );

                    // Has to be get_row instead of get_var because of funkiness with 0, false, null values
                    if ( is_object( $row ) ) {
                        $value = $row->option_value;
                        wp_cache_add( $option, $value, 'options' );
                    } else { // option does not exist, so we must cache its non-existence
                        $notoptions[$option] = true;
                        wp_cache_set( 'notoptions', $notoptions, 'options' );

                        /** This filter is documented in wp-includes/option.php */
                        return apply_filters( 'default_option_' . $option, $default );
                    }
                }
            }
        } else {
            return false;
        }

        //プラグインロード前の時点では is_home 等の条件分岐が出来ないので仮クエリーで wp_query, wp をセットする
        if(empty($GLOBALS['wp_the_query'])){
            $GLOBALS['wp_the_query'] = new WP_Query();
            $GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
            $GLOBALS['wp_rewrite'] = new WP_Rewrite();
            $GLOBALS['wp'] = new WP();
            
            $post_format_base = apply_filters( 'post_format_rewrite_base', 'type' );
            register_taxonomy( 'post_format', 'post', array(
                'public' => true,
                'hierarchical' => false,
                'labels' => array(
                    'name' => _x( 'Format', 'post format' ),
                    'singular_name' => _x( 'Format', 'post format' ),
                ),
                'query_var' => true,
                'rewrite' => $post_format_base ? array( 'slug' => $post_format_base ) : false,
                'show_ui' => false,
                '_builtin' => true,
                'show_in_nav_menus' => true,
            ) );

            $GLOBALS['wp']->parse_request('');
            $GLOBALS['wp']->query_posts();
        }
        global $wp_query;
        if((is_home() || is_front_page() || is_archive() || is_search() || is_singular()) == false || (is_singular() && empty($wp_query->post))){
            return false;   //特定のリクエストページ login, cron 等の post 投稿データのないページは条件判定しない
        }
        
        $us_value = maybe_unserialize( $value );
        $new_value = array();
        foreach ( $us_value as $item ) {
            $unload = false;
            foreach( $this->filter as $plug ) {
                if ( $item == $plug['file'] ) {
                    //条件判定
                    if(is_home() || is_front_page()){
                        if(!empty($plug['home']))
                            break;
                    }
                    elseif(is_archive()){
                        if(!empty($plug['archive']))
                            break;
                    }
                    elseif(is_search()){
                        if(!empty($plug['search']))
                            break;
                    }
                    elseif(is_singular()){
                        if(!empty($plug['attach']) && is_attachment())
                            break;
                        if(is_page()){
                            if(!empty($plug['page']))
                                break;
                            if(count($plug['postid']) > 0 && is_page($plug['postid']))
                                break;
                        }
                        if(is_single()){
                            $fmt = get_post_format( $wp_query->post);
                            if(!empty($plug['standard']) && ($fmt == 'standard' || $fmt == false))
                                break;
                            if(!empty($plug['image']) && $fmt == 'image')
                                break;
                            if(!empty($plug['gallery']) && $fmt == 'gallery')
                                break;
                            if(!empty($plug['video']) && $fmt == 'video')
                                break;
                            if(!empty($plug['audio']) && $fmt == 'audio')
                                break;
                            if(!empty($plug['etc']) && in_array($fmt, array('aside', 'chat', 'link', 'quote', 'status')))
                                break;
                            if(count($plug['postid']) > 0 && is_single($plug['postid']))
                                break;
                        }
                    }
                    $unload = true; //条件不一致なのでロードしない
                    break;
                }
            }
            if($unload === false)
                $new_value[] = $item;
        }
        return apply_filters( 'option_' . $option, $new_value );
    }

とりあえずフィルタリングの主要部分はこんな感じです。

※このコードは少し古いバージョンのもので最新版と違いがありますが、概念としてはおなじなので説明用のサンプルとして見て下さい

高速化の1つの方法として試してみてください (^^)

 


まとめ記事紹介

go-to-top