【WordPress】ビュー数記録機能を自作する
今回は既存のプラグインを使用せずに記事ごとに表示された回数を表示したいと思います。
記事のランキングを作りたいといった動機ではなく、記事ごとにみられているのか知りたいなと思い作成に至りました。
あと、自分の作成練習といったところも兼ねています。
それでは早速見ていきましょう。
WordPressでビュー数を表示する機能を作成する
以下の流れで説明していこうと思います。
考察では実装方法について考えます。
機能の流れではフローチャートのような形で見ていきます。
機能実装部分で実際にコードと共に見ていきたいと思います。
考察
ビュー数を記録するのにお手軽で真っ先に浮かんだのはoptionに保存してカウントアップしていく方法です。
処理の流れのどこかにフックするかテンプレートに直接記述し、発火させてカウントすることができます。
しかし、キーバリュー形式のためカウントと同時に日付の情報を保存したりするのにはやや不向きです。
特定期間でどのぐらい閲覧されているのかも確認したかったため、今回はデータベースに新規でテーブルを作成する方法でカウントしていきたいと思います。
SQLの使用で特定期間の集計も簡単に行うことができますのでピッタリですね。
wordpressではデータベースの取り扱うための仕組みも整っているため実装しやすそうです。
機能の流れ
機能の流れとしては大きく2つあります。
- アクセスされたらカウントする
- カウントされたアクセス数を表示する
「表示されたページをカウントする」こちらは比較的簡単そうですね。表示されたページがその日に初めての表示であればレコードを追加、すでにあればカウントアップといった形です。
「カウントされたアクセス数を表示する」こちらも仕組みは簡単そうですね。投稿や固定ページのIDと取得期間をもとにレコードを集計する形です。
この2つを少し細かくしてみていきます。
「表示されたページをカウントする」ではまず、発火装置が必要です。どの投稿や固定ページで発火しているか知るためにはページ情報が必用なためページ情報がセットされた後にフックをします。
その後に、表示されたページのその日のレコードを取得します。レコードが取得できたときはUPDATEで更新、取得できなかった際はINSERTで新規レコードの追加を行います。
「カウントされたアクセス数を表示する」ではSUMでデータベースからカウント数を取得します。これを設定した期間分だけ行えばOKです。取得した値を固定ページ一覧、投稿一覧画面に表示します。
機能作成
それでは実際に機能をつくっていきます。
今回クラスについて少々学習したかったのでDB関数のクラス、アクセス系のクラスというように分けてクラスと作成していきます。
(今回のDBの状態)
まず、DBのクラスです。
なくてもよいといってしまえばよいですが、他の機能作成時にも使えるようなものにしていきたいので基本的なメソッドのみにします。
- コンストラクタ
- バージョンチェック
- データベース作成(マイグレーション)
- テーブル削除
- レコード追加
- レコード修正
- レコード取得
これらの機能を作成します。
上の三つ分が以下のコードになります。
class My_Db_Class{public $prefix;static $instance = false ;//コンストラクタprivate function __construct() {global $wpdb;$this->prefix = $wpdb->prefix;}public static function getInstance() {if ( ! self::$instance ) {self::$instance = new self();}return self::$instance;}//データベースのバージョンをチェックpublic function check_migrate_required( $key , $version , $sql ) {$db_ver = get_option( $key , "0" );if( $db_ver != $version ) {$this->create_db_table( $sql );if(!update_option( $key , $version ) ) {add_option( $key , $version );}}}//マイグレーションDB作成private function create_db_table( $sql ) {global $wpdb;//略//dbDeltaの含まれているupgrade.phpを呼び出すrequire_once( ABSPATH . 'wp-admin/includes/upgrade.php' );//SQLの実行テーブル生成dbDelta( $sql );}
wordpressでデータベースアクセスに便利なのは$wpdbです。
wordpressでスーパーグローバル関数として定義されています。関数内でデータベース関数を使いますよといった意味合いで「global $wpdb」と宣言をします。
また、インスタンスは一つで良いため「Singleton」パターンを採用します。そのためインスタンスを生成する際には「 new Calss名」ではなく「Class名::getInstance()」として呼び出しを行います。
データベースのマイグレーションを行ってくれる関数がwordpressには備わっています。「dbDelta」です。少々書き方に縛りがありますので公式リファレンスなどで調べてみてください。また、デフォルトでrequireされていないので呼び出してあげる必要があります。
「dbDelta」を毎回呼び出すのではなくoptionとして保存しておいたバージョンと差異があったときに呼び出すようにしてあります。
レコードの追加と修正の部分は今回はデフォルトのまま特に何もしません。
追加は「$wpdb->insert」、修正は「$wpdb->update」に当たります。
レコードの取得は「get_query」によって実装できますが、取得したいものや返り値によってやや方法が異なります。
今回は以下のような形にしてみました。
public function get_record( $sql , $args , $type ) {global $wpdb;$query = $wpdb->prepare( $sql , $args);$res = 0;switch ($type) {case 'val':$res = $wpdb->get_var( $query );break;case 'row':$res = $wpdb->get_row( $query );break;case 'col':$res = $wpdb->get_col( $query );break;case 'res':$res = $wpdb->get_results( $query );break;default:$res = $wpdb->query( $query );}return $res;}
使うのは値の取得である「get_var」とレコードを一行取得する「get_row」だけですが一通り書いておきました。
いずれも引数が一つにしてありますが、とれる引数は複数あります。
「get_var」では
get_var( $query ,$x ,$y )
というように3つとることができます。
SQLで取得する値が一つの時に便利なメソッドですが、複数の行が結果として返ってくる場合でも$x,$yにそれぞれ行と列を指定してあげることで任意の値を一つ取得することができます。
また、「get_row」「get_col」「get_results」はそれぞれ行、列、全行と取得してきますが、引数でオブジェクトや配列など返り値の方を指定することが可能です。
上の記述では型指定できないため、複雑ならこのメソッドそのものをもっと汎用性を高くするか、クラスを継承しオーバーライドで実装しようと思います。とりあえずこのままで。。。
次にアクセス系についてです。こちらもクラスとしました。
- コンストラクタ
- テーブル作成SQL
- レコード取得SQL値
- レコード取得SQL行
- レコードの更新
- レコードの追加
- アクセスカウント
主だったのは以上の機能になります。
こちらも「Singleton」パターンで作成をしました。
まず、「コンストラクタ」部分です。
private const ACCESS_TABLE_VERSION = バージョン番号;private const OP_ACCESS_TABLE_VERSION_KEY = バージョン取得キー;private const ACCESS_TABLE_INDEX = インデックス名;//コンストラクタprivate function __construct(){$this->access_db = My_Db_Class::getInstance();define('ACCESS_TABLE_NAME', $this->access_db->prefix . 'access_count');$this->create_table();}//テーブル作成private function create_table() {$sql = "CREATE TABLE ". ACCESS_TABLE_NAME . " (id bigint(20) NOT NULL AUTO_INCREMENT,post_id bigint(20),post_type varchar(10) DEFAULT 'post',date varchar(20),count bigint(20) DEFAULT 0,PRIMARY KEY (id),KEY " . self::ACCESS_TABLE_INDEX . " (post_id,post_type,date));";$this->access_db->check_migrate_required( self::OP_ACCESS_TABLE_VERSION_KEY , self::ACCESS_TABLE_VERSION , $sql );}
上で挙げたDBクラスのインスタンスを作成しています。
そこからプレフィックスを取得し、テーブル作成SQLを発行しています。
そのため、Accessインスタンスが生成されるとaccessに必要なデータベースが作成、マイグレーションを行うのかといったチェックが入るようになるわけです。
「レコードの取得」部分は以下のような形です。
//レコードの取得(カウント集計)private function get_access_count( $post_id , $span ) {//略$query = "SELECT IFNULL(SUM(count),0) FROM {$table_name} WHEREpost_id = %d AND post_type = %s" . $add_where;return $this->access_db->get_record( $query , $args , 'val' );}//レコードの取得(1件またはゼロ)private function get_access_record( $post_id , $date , $post_type ) {//略$query = "SELECT * FROM {$table_name} USE INDEX({$index})WHERE post_id = %d AND date = %s AND post_type = %s";return $this->access_db->get_record( $query , $args , 'row' );}
どちらでも、レコードを取得するためのクエリ文字列を作成しています。
クエリにはプレースホルダを使用しています。クエリ文字列とプレースホルダに入れる情報の配列をDBクラス側に渡しています。DBクラスではprepareによる安全な組み立てを行っています。
レコードをカウントする際「SUM」は数えるものがないとき「0」ではなくNULLが返ってきますので注意しておきましょう。
レコードの追加と更新はDBクラスにそのまま値を渡しているだけなので省略します。
基本的にはSQLの形で情報を渡すだけですが、どちらも値や条件の形式を’%d’や’%s’といった形でフォーマットとして指定してあげる必要があります。
「アクセスカウント」部分はこのようになります。
public function count_access( $post_id , $post_type ) {if ( (!current_user_can( 'administrator' ) || WP_DEBUG)&& !$this->is_bot() ) {//既存のレコード(同日に表示されたpostまたはpage)があれば取得をします$record = $this->get_access_record( $post_id , $date , $post_type );if ( $record ) {//カウントを+1してレコードを更新} else {//新規レコードの追加}}}
投稿や固定ページが表示された際に呼び出される部分になります。
管理者、ボットでなければカウント処理を行います。ボットは「HTTP_USER_AGENT」によって情報を取得できますが手動では全部はじくことはできません。簡単に作成するなら大手の検索ロボットを対象に含んだチェック機能を作成しましょう。
同日にすでにアクセスのあった投稿や固定ページかを判断して、カウントアップか新しくカウントをとるのか振り分けます。
これまでの部分で機能の部分はほとんど出来上がりました。
これら機能を使う呼び出しについてみていきます。フックの部分です。
こちらも、フックがまとまって見やすくなるのと、関数名を気にしなくてよくなるためクラスで作成しました。
以下のような形となります。
private function __construct() {add_action( 'wp' , array( $this , 'count_access' ) , 10 , 0 );add_action( 'manage_posts_custom_column', array( $this ,'customize_admin_add_column' ) , 10 , 2 );add_action( 'manage_pages_custom_column', array( $this ,'customize_admin_add_column' ) , 10 , 2 );add_filter( 'manage_posts_columns', array( $this , 'add_posts_columns' ) );add_filter( 'manage_pages_columns', array( $this , 'add_posts_columns' ) );}public function count_access() {global $post;$post_id = $post->ID;$post_type = $post->post_type;if ($post_id && $post_type)$this->access->count_access($post_id, $post_type);}public function add_posts_columns( $columns ) {$columns['pv'] = __( 'pv' );return $columns;}public function customize_admin_add_column( $column_name , $post_id ){if ( 'pv' == $column_name ) {//投稿一覧表示部分}echo 表示;}
クラスにすることでコンストラクタにフックをまとめて登録しておくことができます。
通常は
add_filter( 'manage_posts_columns', 'add_posts_columns' ) );
このような形になると思いますが、クラス内で呼び出す際には上のように配列にして渡します。
また、フックでしか使っていないのでそれぞれの関数をprivateにしたく思うかもしれませんが、実際の呼び出しはフックされたタイミングで外から呼び出されていますのでpublicにしておかないと呼び出すことはできませんので注意しておきましょう。
最後にそれぞれの骨組みだけまとめて乗せておきます。
//コンストラクタprivate function __construct() {}//インスタンス生成public static function getInstance() {}//データベースのバージョンをチェックpublic function check_migrate_required( $key , $version , $sql ) {}//チェックに差異があった場合DBテーブルprivate function create_db_table( $sql ) {}//テーブル削除public function delete_db_table( $table_name ) {}//レコードの追加public function insert_db_record( $table , $data , $format ) {}//レコードの編集public function update_db_record( $table , $data , $where , $format , $where_format ) {}//レコードの取得(SQL,プレースフォルダ値,取得方法)public function get_record( $sql , $args , $type ) {}
//コンストラクタprivate function __construct(){}//自クラスインスタンス生成public static function getInstance() {}//テーブル作成private function create_table() {//SQL作成}//レコードの取得(カウント集計)private function get_access_count( $post_id , $span ) {//$spanによる指定された期間分のカウントを取得}//レコードの取得(1件またはゼロ)private function get_access_record( $post_id , $date , $post_type ) {//当日分のレコード取得}//レコードの更新(カウントアップ時)private function update_access_record( $id , $count ) {}//レコードの追加private function insert_access_record( $post_id , $date , $post_type , $count ) {}//アクセスカウントpublic function count_access( $post_id , $post_type ) {//取得したレコードに応じてカウントアップまたは新規カウント}//bot判定private function is_bot() {}//当日のカウント取得public function today_count() {}//7日分のカウント取得public function week_count() {}//30日分のカウント取得public function month_count() {}//トータルのカウント取得public function total_count() {}
require_once ABSPATH.'wp-admin/includes/file.php';require_once dirname(__FILE__). '/' . 'access.php';require_once dirname(__FILE__). '/' . 'db.php';$access = AccessCountPlugin::getInstance();class AccessCountPlugin {//コンストラクタprivate function __construct() {//フック登録}//インスタンス生成public static function getInstance() {}//ページ表示時アクセスカウントpublic function count_access() {}//固定ページ一覧、投稿一覧に「pv」列追加public function add_posts_columns( $columns ) {}//pvr列コンテンツ表示public function customize_admin_add_column( $column_name , $post_id ){}}
このような形になりました。
まとめ
今回の要点としては$wpdbあたりとクラスのお作法ですね。
機能の作成自体はそんなに難易度の高いものではないと思います。
今回はクラスを使用したく、そのあたりで結構こけたりしたので私は時間がかかりました。
javaで触れた以来クラスのスコープやプロパティの宣言方法について記憶があいまいでしたので。。。
次の記事あたりでビュー数の取得期間についてUIでユーザーが指定することができるような形にしたいと思います。
バラバラに書いていくよりクラスとして書いていく方が自分に合っているような気がしました。これからプラグインなどの開発を行う際にはうまく使って行けたら便利かなと思います。