wpdb->prepareでテーブルに変数を使う

wpdb->prepareでテーブルに変数を使う

WordPress のデータベース操作の際に wp->prepare でテーブル名に変数を使おうとしてエラーになった件、%i というプレースホルダーで解決です。

01prepare を使う意味

テーマやプラグインを自作しないのであれば直接データベースを操作することはないですし、仮に自作する場合でも、SQL のテーブル名に変数を使わなくてはいけないケースも稀だとは思います。

ですのであまり一般的な事例ではありませんが、たまたま wp->prepare でテーブル名に変数を使ったためにエラーとなり、いろいろ調べたところ、WordPress Developer Resources の wpdb::prepare に %i というプレースホルダーがあることを知ったということです。

WordPress でデータベースを操作する場合は、SQLインジェクション対策のために $wpdb->prepare を使います。クエリに思わぬ SQL が入らぬようにエスケープするためです。たとえば、公開しているメインの記事のタイトル一覧を取得するには、

global $wpdb;
$query = "SELECT post_title FROM wp_posts WHERE post_status = 'publish' AND post_type = 'post'";
$result = $wpdb->get_results( $query );
print_r($result);

これでこと足りますが、クエリに変数を使う場合は、

global $wpdb;
$type = 'post';

$query = "SELECT post_title FROM wp_posts WHERE post_status = 'publish' AND post_type = %s";
$prepare = $wpdb->prepare( $query, $type);
print_r($prepare);
$result = $wpdb->get_results( $prepare );
print_r($result);

として、$wpdb->prepare を介して変数の値をエスケープします。

この例ではコード内で変数に値を入力していますので危険はないのですが、この $type の値をフォームデータや URL クエリから得るとしますと SQLインジェクションの危険が生じます。そこで上のコードのように $wpdb->prepare を使い、プレースホルダーで変数に値を与える方法を取ります。

SELECT post_title FROM wp_posts WHERE post_status = 'publish' AND post_type = 'post'
Array
(
    [0] => stdClass Object
        (
            [post_title] => Hello world!
        )
)

上のコードの結果です。WordPress を新規インストールしたままですので「Hello World!」ひとつだけタイトルが表示されています。1行目がデータベースに与えた SQL です。

02プレースホルダー %i

で、本題です。上のコードのテーブル名やフィールド(カラム)にも変数を使ったらどうなるんだろうということで次のコードを試してみました。

global $wpdb;
$table = 'wp_posts';
$column = 'post_title';
$status = 'publish';
$type = 'post';

$query = "SELECT %s FROM %s WHERE post_status = %s AND post_type = %s";
$prepare = $wpdb->prepare( $query, $column, $table, $status, $type);
print_r($prepare);
$results = $wpdb->get_results( $prepare );
print_r($results);

「SQL 構文にエラーがあります」のエラーになります。

原因はテーブル名とフィールド(カラム)のプレースホルダーに文字列用の %s を使っているからです。え? どうして? とかなり時間をつかいました。

SELECT 'post_title' FROM 'wp_posts' WHERE post_status = 'publish' AND post_type = 'post'

SQL 文を見てみますと、テーブル名とフィールドがシングルクオートで囲まれています。これが問題です。データベースの識別子は引用符なしか、囲むのであればバッククオートを使わないとエラーになります。

ということは、テーブルやフィールドには変数は使えないのかと思ったのですが、WordPress Developer Resources に答がありました。

プレースホルダー %d(整数), %f(浮動小数点数), %s(文字列)に続いて、

%i (identifier, e.g. table/field names)

%i があります。%i は、テーブルやフィールド(カラム)などに使うとあります。

03テーブル名を変数で与えるには %i を使う

global $wpdb;
$table = 'wp_posts';
$column = 'post_title';
$status = 'publish';
$type = 'post';

$query = "SELECT %i FROM %i WHERE post_status = %s AND post_type = %s";
$prepare = $wpdb->prepare( $query, $column, $table, $status, $type);
print_r($prepare);
$results = $wpdb->get_results( $prepare );
print_r($results);

うまくいきました!

SELECT `post_title` FROM `wp_posts` WHERE post_status = 'publish' AND post_type = 'post'

SQL 文を見てみますと確かにバッククオートで囲われています。

SQL でテーブル名をユーザーが指定するケースはあまりないかも知れませんが、フィールド(カラム)の場合は考えられますので、プレースホルダー %i を使うケースもありそうです。