結論
WordPress のプラグイン自作で、データベースに独自のテーブルを作るときは、次の3点を組み合わせるのが公式の Plugin Handbook に沿ったやり方です。
- テーブル作成は
dbDelta()にCREATE TABLE文を渡して行う(wp-admin/includes/upgrade.phpを読み込んでから呼ぶ) - 実行のタイミングは プラグインの有効化時(
register_activation_hook()) - テーブル構造の版をオプションに保存し、プラグイン更新時は
plugins_loadedで版を比べて再実行する
dbDelta() は「今あるテーブルと、渡した CREATE TABLE 文の差分」を見て、足りない列やインデックスを ALTER TABLE で足してくれます。そのため、同じ関数を新規インストールにも構造の変更にも使えます。
まず、投稿メタやオプションで足りないかを先に検討してください。独自テーブルは、件数が多いログや集計など、wp_postmeta に入れると検索が重くなるデータに向いています。
原因・仕組み
dbDelta(WordPress 1.5.0 から存在)は、$queries(SQL 文字列または配列)と $execute(既定 true)を受け取り、実行した変更内容を文字列の配列で返します。定義されているのは wp-admin/includes/upgrade.php で、フロント側のリクエストでは読み込まれていないため、require_once が必要です。
この関数は SQL をそのまま流すのではなく、文字列を解析して既存の構造と比べるため、書式に厳しい決まりがあります。Plugin Handbook が挙げている主な条件は次の通りです。
- 1行に1フィールドを書く
PRIMARY KEYと定義の間は空白2つINDEXではなくKEYを使い、少なくとも1つの KEY を含める- フィールド名をバッククォートや引用符で囲まない
- 型は小文字、
CREATE TABLEなどの SQL キーワードは大文字 - 長さを取る型はすべて長さを指定する
文字コードと照合順序は、WordPress 3.5 で追加された $wpdb->get_charset_collate() で、サイトの設定に合わせます。これを付けないと、サーバーの既定の文字コードでテーブルが作られ、日本語が化ける原因になります。
手順(サンプルコード)
プラグインのメインファイル(例: my-plugin/my-plugin.php)に書きます。
<?php
/*
* Plugin Name: My Plugin Logs
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // 直接アクセスを防ぐ
}
define( 'MYPL_DB_VERSION', '1.1' );
function mypl_install() {
global $wpdb;
$table = $wpdb->prefix . 'mypl_logs';
$charset = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
user_id bigint(20) unsigned NOT NULL default 0,
message text NOT NULL,
status varchar(20) NOT NULL default 'new',
created_at datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (id),
KEY user_id (user_id)
) $charset;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
update_option( 'mypl_db_version', MYPL_DB_VERSION );
}
register_activation_hook( __FILE__, 'mypl_install' );
// 更新時(有効化フックは更新では走らない)の構造変更
function mypl_update_db_check() {
if ( get_option( 'mypl_db_version' ) !== MYPL_DB_VERSION ) {
mypl_install();
}
}
add_action( 'plugins_loaded', 'mypl_update_db_check' );
データの追加は $wpdb->insert() に書式(%d %s)を渡し、値を SQL に直接埋め込まないようにします。
global $wpdb;
$wpdb->insert(
$wpdb->prefix . 'mypl_logs',
array(
'user_id' => get_current_user_id(),
'message' => sanitize_text_field( $message ),
'created_at' => current_time( 'mysql' ),
),
array( '%d', '%s', '%s' )
);
読み出しで条件に外部の値を使うときは $wpdb->prepare() を通します。
$rows = $wpdb->get_results(
$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}mypl_logs WHERE user_id = %d ORDER BY id DESC LIMIT 20", $user_id )
);
動作確認(検証環境と結果)
WordPress 7.1.2(ja)/PHP 8.2.12/MariaDB 10.4.32 の CLI 検証環境で、上と同じ構造の関数を「列 status なし(版1.0)→同じ内容でもう一度→status を足した版1.1」の順に実行しました(php run.php wl2_dbdelta.php、ログは tests/plugin-custom-table-dbdelta.md)。
1st (v1.0): [wp_mypl_logs] => Created table wp_mypl_logs
2nd same (v1.0): (空の配列)
3rd (v1.1 add column): [wp_mypl_logs.status] => Added column wp_mypl_logs.status
insert=1 id=1
cols=id,user_id,message,created_at,status
row status=new
charset=DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci
- 1回目はテーブルが作られ、同じ文での2回目は何も変更されず(空配列)、列を足した3回目は
Added columnだけが実行されました。既存データを消さずに構造を足せることが確認できます。 - その後、定義から
statusを外した文でもう一度実行すると、戻り値は空配列で、status列はそのまま残りました(dbDelta は列を消さない)。 - 足した列
statusは、SQL に書いた位置(created_at の前)ではなく末尾に追加されました。 PRIMARY KEY (id)(空白1つ)でも、この環境ではエラーは出ず、2回目の実行も空配列でした。ただし公式の Handbook は空白2つを求めているので、記事のコードは公式どおりにしています。- 有効化フック自体の発火(管理画面での有効化)はこの CLI 環境では確認していません(関数を直接呼んで確認)。最後にテーブルは削除しました。
WordPress プラグイン自作でデータベースに独自テーブルを作る方法の注意点
dbDelta()は列の削除やテーブルの削除はしません。列名を変えると、古い列が残ったまま新しい列が足されます。削除はアンインストール処理(uninstall.phpやregister_uninstall_hook())でDROP TABLEを自分で書きます。- テーブル名には必ず
$wpdb->prefixを付けます。マルチサイトや接頭辞を変えたサイトでも衝突しません。 register_activation_hook()は、WordPress 3.1 以降、プラグインを更新しただけでは実行されません(Make WordPress Core の告知)。告知でも勧められているとおり、構造を変えたら版番号を保存して比べる、上のplugins_loadedのような版チェックが必要です。$wpdb->prepare()でテーブル名をプレースホルダにしないでください(値用です)。テーブル名は$wpdb->prefixから自分で組み立てた固定の文字列を使います。
WordPress プラグイン自作でデータベースに独自テーブルを作る方法でよくあるミス
require_once ABSPATH . 'wp-admin/includes/upgrade.php';を忘れ、Call to undefined function dbDelta()で致命的エラーになる。- フィールド名をバッククォートで囲んだり、
INDEXと書いたりして、2回目以降に余計な ALTER が走る・差分が正しく取れない。 register_activation_hook()をクラスのメソッドやフックの中で呼び、登録が間に合わずテーブルが作られない(メインファイルの直下で呼びます)。- 版をオプションに保存せず、更新のたびに何が変わったか追えなくなる。
WordPress プラグイン自作でデータベースに独自テーブルを作る方法のチェックリスト
- [ ] まず投稿メタ・オプションで足りないかを検討した
- [ ] テーブル名に
$wpdb->prefixを付けた - [ ]
get_charset_collate()を付けた - [ ]
PRIMARY KEYの後は空白2つ、KEYを1つ以上書いた - [ ]
upgrade.phpを読み込んでからdbDelta()を呼んだ - [ ] 版をオプションに保存し、更新時に比べている
- [ ] 値の挿入・検索は
insert()の書式/prepare()を通している - [ ] アンインストール時の削除処理を用意した
WordPress プラグイン自作でデータベースに独自テーブルを作る方法のFAQ(よくある質問)
Q. テーブルが作られたか確かめるには。
A. dbDelta() の戻り値に Created table ... が入ります。phpMyAdmin や $wpdb->get_var( "SHOW TABLES LIKE '...'" ) でも確認できます。
Q. 列の型を変えたいときは。
A. CREATE TABLE 文の型を書き換えて版を上げれば、dbDelta が差分を見て変更を試みます。データが変わる変更なので、先にバックアップを取ってください。
Q. 停止したらテーブルを消すべきですか。
A. 停止(deactivation)では消さず、削除(uninstall)のときに消すのが一般的です。停止は一時的なことが多いためです。
筆者の見解(WordPress プラグイン自作でデータベースに独自テーブルを作る方法)
独自テーブルは強力ですが、一度作ると移行・バックアップ・削除の責任をプラグイン側がずっと持つことになります。私見では、「件数が多く、絞り込み検索が必要なデータ」以外は、まず投稿メタやカスタム投稿タイプで設計するほうが保守しやすいと考えます。作る場合は、検証で見たとおり dbDelta は列を足すことは得意でも消すことはしないので、版番号と uninstall 処理を最初から書いておくのが、後から困らない近道だと思います。
WordPress プラグイン自作でデータベースに独自テーブルを作る方法の関連項目
- dbDelta() の使い方|説明・引数・注意点
- register_activation_hook() の使い方|説明・引数・注意点
- register_deactivation_hook() の使い方|説明・引数・注意点
- register_uninstall_hook() の使い方|説明・引数・注意点
- $wpdb->insert・update の戻り値とエラーの確認方法(準備中)
- $wpdb->prepare で IN 句に配列を渡す方法|安全な書き方(準備中)
- WordPress プラグイン自作の方法|最小構成のファイルと有効化・停止・削除の書き方
