なんでこんなことになってしまったのかはよくわからないのですが、僕のようにWordPressをWeb開発のフレームワークにしている場合、こんな風に考えることがあります。
- WordPressはなにもしなくてもサイトが出来上がって便利だなあ、管理画面もついてるし
- そうだ、投稿じゃなくて購入履歴のDBを追加しよう!
- 購入履歴一覧とか、購入履歴詳細とか、そういうのほしいなあ
- >>>>>突然のめんどくせ<<<<<
なんでめんどくさくなるかというと、WordPressは次のような処理を行っているからです。
- WordPressで表示されるデータはすべて投稿のリストであり、それ以外はありえない、なぜならブログなので
- 投稿のデータは
WP_Post
クラスの配列として格納される - だったら
WP_Post
を継承して、WP_Purchase_Log
(購入履歴クラス)作ってなんかできないかな final WP_Post
- オワタ \(^o^)/
ここでWP_Post
を拡張しうる仕組みが存在していれば、フレームワークとしてもうちょっと色々できて便利なのですが、そうはなっていません。
調べてみると、「なんでWP_Post
がfinal(継承不可)やねん、継承できるようにしてやー」と言っている人がいます。
- Remove final from WP_Post class
- Allow get_post() to accept a custom post object instead of only WP_Post
結果から言うと、ともに却下っぽい感じですね。Nacinというコアコミッターによると、「WP_Post
はAPIじゃなくてセキュリティ用のクラスだからfinalなんだよ」とのことです。ふーん。妥当かどうかは知りませんが、理由あってのこと。じゃあ、しばらく変わらなそうですね。
で、なんとかならないかなと思ってやってみました。こんな感じです。
add_filter('query_vars', function($var){ $var[] = 'subscriber'; return $var; });
まず、クエリバーにsubscriberという文字列を追加します。これによってWP_Query
はsubscriberというパラメータを受付けるようになります。
具体的に言うと、http://example.jp?subscriber=hogeを受付けるということですね。
これをリライトルールで拡張すると、http://example.jp/subscriber/hogeというURLで「hogeというsubscriberを表示するのね」となります。ここら辺、MVCフレームワークと同じルーティングですね。リライトルールのコードはメンドクサイので書いてません。Codexのカスタムクエリやクエリ概要など参照してください。
続いて、本来なら取得するであろう投稿データではなく、ユーザーを取得してみます。WordPressはMVCではないので、SQLをいきなり書き換えます。
add_filter('posts_request', function( $request, \WP_Query &$wp_query){ /** @var \wpdb $wpdb */ global $wpdb; // WP_Queryにsubscriberが設定されている場合のみSQL変更 if( $wp_query->get('subscriber') ){ // 現在のページを取得。これはWordPressデフォルトの動作 $page = max( (int)$wp_query->get('paged'), 1 ); $per_page = get_option('posts_per_page’); // SQLを作成。SQL_CALC_FOUND_ROWSは必須。ないと無限ループになる。 $query = <<<EOS SELECT SQL_CALC_FOUND_ROWS ID, 'subscriber' AS post_type FROM {$wpdb->users} LIMIT %d, %d EOS; $request = $wpdb->prepare($query, ($page - 1) * $per_page, $per_page); } return $request; }, 10, 2);
本来なら投稿テーブルにデータを取りにいくSQLを書き換えて、ユーザーのIDを取得し、なおかつpost_typeという存在しない投稿タイプを割り当てているところですね。
これで http://example.jp?subscriber=hoge の場合、取得するデータが投稿ではなく、ユーザーデータに変わることになります。
つづいて、ループ内で参照するデータをカスタマイズしてみます。Subscriber
というモデルクラスを参照可能にしましょう。ループ内でよく書くthe_post
を呼び出すたびに、上記で取得したIDのSubscriber
が割り当てられていきます。
global $subscriber; add_action('the_post', function( \WP_Post &$post ){ global $subscriber; $subscriber = new Subscriber($post->ID); });
グローバル変数を使うことになってしまいました。無念です。しかし、これによってループ内で$subscriber->display_name
とやることを目指します。
/** * 購読者クラス * * @property-read string $display_name * @property-read string $avatar */ class Subscriber { /** * ユーザーデータ * * @var WP_User */ private $data = null; /** * 投稿タイプ * * @var string */ public $post_type = 'subscriber'; /** * コンストラクタ * * @param int $id */ public function __construct($id){ $this->data = new WP_User($id); } /** * ゲッター * * @param string $name * @return int|mixed|null */ public function __get($name){ switch($name){ case 'avatar': return get_avatar($this->data->ID); break; case 'user_url': return false !== array_search($this->data->user_email, array('http://', '')) ? $this->data->user_url : get_author_posts_url($this->data->ID, $this->data->author_nicename); break; default: if( isset($this->data->{$name}) ){ return $this->data->{$name}; }else{ return null; } break; } } }
ちょっと長いですが、購読者のデータを表現したSubscriber
クラスを定義してみました。
これであとはテンプレートを書きます。テンプレートについては、get_template_part(’loop’, get_post_type())
とかでloop-[post_type].phpが読み込まれるような仕組みにすでになっているとしましょう。とうことは、変えるべきテンプレートはloop-subscriber.phpということになります。
<?php /** @var Subscriber $subscriber */ global $subscriber; ?> <li class="clearfix loop loop-post"> <?= $subscriber->avatar; ?> <h4 class="post-title"><a href="<?= $user->user_url ?>"><?= esc_html($subscriber->display_name); ?></a></h4> <span class="date mono"> <?= $subscriber->user_email ?> </span> </li>
global
でグローバル変数を呼び出しているところがちょっとかっこわるいのですが、現時点ではこんぐらいですかねー。
load_template
という関数を見ると、requireの前にextract($wp_query->query_vars)
としているので、$userをグローバル変数ではなく、$wp_queryの変数に格納すればいいのかもしれません。
では、どんな見映えになるのか見てみましょう。
どうでしょう、このブログの通常のアーカイブページの記事ボックスがユーザー情報に変わりました。普通のサービスならメールアドレス出すことはないとおもいますけどね。
ポイントとしてはページネーションがそのまま有効に機能しているところです。Subscriber
クラスのゲッターを充実させていけばもっと色々できるでしょう。
詳しい人はWP_User_Query
を使えばいいんじゃないの?と思われるかもしれません。が、結局WordPressのルーティング(URLを解析してデータを取得する)とシームレスにつなぐためにはいまのところこういうアプローチもありではないかと。
でも、自前のルーティング作った方が楽かもしれませんね。うーん。悩ましいところです。
というわけで、WordPressでアプリケーションを開発している場合に役立つかもしれないという情報でした。