WordPress Codex のサンプルコードを実践的にする考え方の例 ~管理画面にページを追加するカスタマイズを題材に~

WordPress Codex のサンプルコードを実践的にする考え方の例 ~管理画面にページを追加するカスタマイズを題材に~

キングスマン:ゴールデン・サークル』で "Take Me Home, Country Roads" を歌うマーリンにちょっと泣いた kagata です。映像を見て泣くのは『仮面ライダードライブ』45話以来2年4ヶ月ぶりでした。

さて、今回は WordPress のカスタマイズの話題です。世の中には WordPress カスタマイズに使えるコードスニペットが豊富に出回っています。それらを集めてコピペするだけでも、さまざまな機能が実現できてしまいます。でも、そうした結果テーマの functions.php がゴチャッとしてしまっていたりしませんか?

そうならないようにするためには、見つけたコードをただコピペするのでなく、カスタマイズの規模に応じて手を加えてやる必要があります。その手順と考え方の例を、WordPress Codex に実際に掲載されているサンプルコードを例にご紹介します。

題材:管理画面にページを追加するサンプルコード

WordPress Codex の関数 add_menu_page() の項には以下のようなサンプルコードが掲載されています。

add_action( 'admin_menu', 'register_my_custom_menu_page' );

function register_my_custom_menu_page(){
	add_menu_page( 'custom menu title', 'custom menu', 'manage_options', 'custompage', 'my_custom_menu_page', plugins_url( 'myplugin/images/icon.png' ), 6 ); 
}

function my_custom_menu_page(){
	echo "Admin Page Test";	
}

このコードをプラグインなりテーマなりにコピペすると、 "Admin Page Test" と表示するだけの簡単なページが管理画面に追加されます。

20180111-1.png

これはこれで問題なく動作しますし、簡単な内容のページを1つ追加するだけならこのままでかまいません。しかし、実際の WordPress カスタマイズでそのまま使うには次に挙げるような心配があります。

サンプルコードの改善したい点

1. ページをさらに増やすのがめんどくさい

このサンプルコードを応用して、管理画面に複数のページを追加するにはどうすればいいでしょうか。

register_my_custom_menu_page()my_custom_menu_page() をコピペして、関数名と中身を書き換えて add_action('admin_menu') すればまあできます。でも、ページの数だけ関数をコピペしていると、とっちらかったコードになってしまいます。これは避けたい。

より整理されたコードでページを複数追加するには、どうすればよいでしょうか。

2. 画面の内容が長く複雑になるとめんどくさい

画面の内容は my_custom_menu_page() が出力しています。中身はシンプルな echo 文です。

管理画面にページを追加したいとき、中身が1行の echo で済むことはないでしょう。実際には何十行にもわたる HTML コードを出力したいはずです。

では、そうなったら行数だけ echo 文を繰り返すのでしょうか。たしかに、それでもやりたいことは実現できるでしょう。でも、たいへん読みづらいコードになってしまいます。

さらに、その中身が条件によってさまざまに変化するとしたら… if 文の分岐といくつもの echo 文が入り乱れたコードなんて、想像したくないものです。

ページの出力が長く複雑になってもコードを読みやすく保つには、どうすればよいでしょうか。

サンプルコードを改善する

では、上に挙げた2つの懸念を意識しつつ、コードの改善を試みましょう。

1. とりあえずクラスにまとめる

// Codex にあるコードをとりあえずまとめたクラス
class Example_Admin_Page
{
    public function activate()
    {
        // 第2引数のコールバックに注目
        add_action( 'admin_menu', [$this, 'register_my_custom_menu_page'] );
    }

    public function register_my_custom_menu_page(){
        // 第5引数のコールバックに注目
        add_menu_page( 'custom menu title', 'custom menu', 'manage_options', 'custompage', [$this, 'my_custom_menu_page'], plugins_url( 'example/images/icon.png' ), 6 );
    }

    public function my_custom_menu_page(){
        echo "Admin Page Test";
    }
}

// クラスにまとめた処理を実行する
$example_admin_page = new Example_Admin_Page();
$example_admin_page->activate();

とりあえず、サンプルコードをクラスにまとめました。 クラス名などは、 example という名前のプラグインに実装する処理をイメージしています。

元あった2つのコールバック関数は public なメソッドにしました。 add_action() を呼び出す文は、クラスに追加した activate というメソッドに持っていきます。

コールバック関数がグローバルスコープからクラスに属するメソッドに変わったので、引数として指定する際の記法が単なる関数名から [$this, <関数名>] のようになっています。この記法については PHP: コールバック / Callable - Manual を参照してください。なお、PHP5.3以前では array($this, <関数名>) となります。

クラスにまとめた処理は、最後の2行のようにして実行します。これで、元のサンプルコードと同じことができます。

難しいことは考えずに、ほとんど機械的に一連の処理をクラスにまとめただけです。それでも、 add_action() と2つの関数がグルーピングされて、なんとなくコードに整理がついた感じがします。さらに改善を進めましょう。

2. 関数名を簡潔にする

class Example_Admin_Page
{
    public function activate()
    {
        add_action('admin_menu', [$this, 'add']);
    }

    public function add()
    {
        add_menu_page('custom menu title', 'custom menu', 'manage_options', 'custompage', [$this, 'view'], plugins_url('example/images/icon.png'), 6);
    }

    public function view()
    {
        echo "Admin Page Test";
    }
}

コールバック関数をメソッドにしたことで、他のグローバル関数や他のクラスに属するメソッドと名前が衝突する心配がなくなりました。これで、安心して関数名を短くできます。

register_my_custom_menu_pageadd に、 my_custom_menu_pageview に改名しました。名前が簡潔なのはよいことです。

3. WordPress 関数に渡す引数をクラスのプロパティにする

次に、 add メソッドで呼び出している WordPress 関数 add_menu_page() に注目します。次のように作り替えてみましょう。

  • add_menu_page() に渡す6つの引数を、クラスの private なプロパティにする
  • プロパティにした6つの引数のうち必須のものは、コンストラクタの引数にする
  • プロパティにした6つの引数のうち必須でないものには、値をセットするためのメソッド(セッターメソッド)を追加する
  • セッターメソッドが $this を返すようにする

ちょっと長くなりますが、 Example_Admin_Page クラスは次のようなコードになります。

class Example_Admin_Page
{
    private $page_title;
    private $menu_title;
    private $capability;
    private $menu_slug;
    private $icon_url;
    private $position;

    /**
     * Example_Admin_Page constructor.
     *
     * @param string $page_title ページのタイトル
     * @param string $menu_title メニューのラベル
     * @param string $capability アクセス権限
     * @param string $menu_slug ページのスラッグ
     */
    public function __construct($page_title, $menu_title, $capability, $menu_slug)
    {
        $this->page_title = $page_title;
        $this->menu_title = $menu_title;
        $this->capability = $capability;
        $this->menu_slug  = $menu_slug;
    }

    /**
     * メニューアイコンの URL を指定する
     *
     * @param string $icon_url メニューアイコンの URL
     * @return $this
     */
    public function set_icon_url($icon_url)
    {
        $this->icon_url = $icon_url;
        return $this;
    }

    /**
     * メニューの位置を指定する
     *
     * @param int $position メニューの位置
     * @return $this
     */
    public function set_position($position)
    {
        $this->position = $position;
        return $this;
    }

    public function activate()
    {
        add_action('admin_menu', [$this, 'add']);
    }

    public function add()
    {
        add_menu_page($this->page_title, $this->menu_title, $this->capability, $this->menu_slug, [$this, 'view'], $this->icon_url, 6);
    }

    public function view()
    {
        echo "Admin Page Test";
    }
}

こうすることで、ページを複数追加する処理が簡単に書けるようになります。例えば3つのページを追加するなら、上のとおりクラスを定義したあとで次のように書けば OK です。

// 1ページめ
$example_admin_page1 = new Example_Admin_Page('custom menu title 1', 'custom menu 1', 'manage_options', 'custompage1');
$example_admin_page1
    ->set_icon_url(plugins_url('example/images/icon1.png'))
    ->set_position(6)
    ->activate();

// 2ページめ
$example_admin_page2 = new Example_Admin_Page('custom menu title 2', 'custom menu 2', 'manage_options', 'custompage2');
$example_admin_page2
    ->set_icon_url(plugins_url('example/images/icon2.png'))
    ->set_position(7)
    ->activate();

// 3ページめ(アイコンの URL や位置の指定は省略してよい)
$example_admin_page3 = new Example_Admin_Page('custom menu title 3', 'custom menu 3', 'manage_options', 'custompage3');
$example_admin_page3->activate();

ひとつ前のサンプルコードだと、ページをさらに追加するにはクラスをまるごとコピペして、クラス名や関数の中身を書き換えないといけませんでした。それと比べると、ページを複数追加するコードがコンパクトに書けるようになりました。1つめの問題点は、これでひとまず解決です。

4. 画面の内容を別のファイルに切り出す

上のコードだと、ページをいくつ追加してもその内容は "Admin Page Test" で同じです。当然、ページごとにふさわしい内容を出力したいところです。

そうすると、そろそろ view の中身が複雑になってきそうです。その前に、2つめの問題点「ページの出力が長く複雑になるとめんどくさい」を解消しておきましょう。

class Example_Admin_Page
{
    // 省略

    public function view()
    {
        include WP_PLUGIN_DIR . '/example/templates/view.php';
    }
}

view メソッドの中身を echo 文でなく include 文にしました。では、 include するファイルの中身はどうすればよいでしょうか。

// plugins/example/templates/view.php
Admin Page Test

これだけですね。PHP のロジックが出てこないので、これなら長い HTML コードもすんなり書けそうです。

さらにもう一ひねりして、ページごとに異なる内容を出力するようにしましょう。いろいろな方法が考えられますが、今回は次のようにします。

    public function view()
    {
        include WP_PLUGIN_DIR . '/example/templates/' . $this->menu_slug . '.php';
    }

読み込むテンプレートファイルをページのスラッグで決めるようにします。ページ custompage1 にはテンプレートファイル templates/custompage1.php を、ページ custompage2 にはテンプレートファイル templates/custompage2.php を…と、対応するファイルを用意します。それぞれ表示したい内容を記述すれば、条件分岐なしで表示内容が切り替えられます。

5. インスタンスを生成する関数を用意する

最後はおまけです。必須というわけではないですが、WordPress のプラグイン実装で時折見かけるプラクティスです。

ページを追加する部分のコードを思い出してみましょう。

$example_admin_page1 = new Example_Admin_Page('custom menu title 1', 'custom menu 1', 'manage_options', 'custompage1');
$example_admin_page1
    ->set_icon_url(plugins_url('example/images/icon1.png'))
    ->set_position(6)
    ->activate();

ページを追加する際に、変数をひとつ定義しています。以前の記事にも書いたとおり、WordPress のカスタマイズでは変数の定義を少なくしたほうが安全な場合があります。

また、改行の都合でわかりにくいかもしれませんが、この処理はよく見ると2つの文でできています。インスタンスを生成する new 文と、インスタンスにプロパティをセットして activate する文の2つです。プラグインのコードではあまり問題にならないのですが、テーマのコードでは一連の処理が1行で書けるとコードの見通しがよくなって便利です。

この2つの課題を同時に解決する手段として、インスタンスを生成するグローバル関数を用意して new 文の代わりにするという手法が使えます。次のような感じです。

/**
 * ページのインスタンスを生成する
 *
 * @param string $page_title ページのタイトル
 * @param string $menu_title メニューのラベル
 * @param string $capability アクセス権限
 * @param string $menu_slug ページのスラッグ
 * @return Example_Admin_Page ページのインスタンス
 */
function example_create_admin_page($page_title, $menu_title, $capability, $menu_slug)
{
    return new Example_Admin_Page($page_title, $menu_title, $capability, $menu_slug);
}

// インスタンスの生成から activate までワンライナーで書ける!
example_create_admin_page('custom menu title 1', 'custom menu 1', 'manage_options', 'custompage1')
    ->set_icon_url(plugins_url('example/images/icon1.png'))
    ->set_position(6)
    ->activate();

なお、ステップ3でセッターメソッドが $this を返すようにしていたのも、実は最終ステップで変数定義なしにページを追加するための下準備でした。セッターメソッドが何も返さないようにして、以下のようにしてもかまいません。

$example_admin_page1 = new Example_Admin_Page('custom menu title 1', 'custom menu 1', 'manage_options', 'custompage1');
$example_admin_page1->set_icon_url(plugins_url('example/images/icon1.png'));
$example_admin_page1->set_position(6);
$example_admin_page1->activate();

完成

これで、WordPress のお作法にならいつつ、読みやすくて拡張しやすいコードになりました。プラグインにする場合、最終的なファイル構成は次のようになるでしょう。

  • プラグインのエントリポイント( plugins/example/example.php )
    example_create_admin_page() の呼び出しから activate までの処理を記述。ここでは関数やクラスの定義はしない(変数定義もできるだけ避ける)。
  • Example_Admin_Page クラスを定義するファイル
  • 関数 example_create_admin_page() を定義するファイル
  • テンプレートファイル(ページの数だけ作成する)

まとめ

WordPress Codex に掲載されているサンプルコードをそのまま使っていると、カスタマイズの規模が大きくなるうちに読みづらく拡張しづらいコードになってしまうことがあります。

そんな懸念をクリアして、サンプルコードをより実践的なものにするための考え方として、次のポイントを紹介しました。

  • フックの呼び出しと関連するコールバック関数はクラスにまとめてみよう
  • 「ロジック」と「画面の内容」は別のファイルに記述しよう(これはテーマの作成でも大事!)
  • new 文は直接呼び出さずインスタンス生成用の関数を用意するといいかも

これらを心がけることで、カスタマイズの規模が大きくなっても、読みやすく拡張しやすいコードに保つことができます。管理画面の追加だけでなく様々な場面で応用してみてください。

  • このエントリーをはてなブックマークに追加

この記事を読んだ人にオススメ