WordPressの$wpdb->prepareの使い方|SQLインジェクションを防ぐ安全な書き方

WordPressでカスタム開発をしていると、標準の投稿取得機能だけでは足りず、データベースへ直接SQLを書いて操作したい場面が出てきます。
たとえば、カスタムテーブルを扱うときや、複雑な条件でデータを取得したいとき、独自プラグインの中でレコードを検索・更新したいときなどです。

そのときに重要になるのが、$wpdb$wpdb->prepare() です。

特に初心者のうちは、次のような書き方をしてしまうことがあります。

$sql = "SELECT * FROM {$wpdb->prefix}users WHERE user_login = '" . $_GET['user'] . "'";

このようなコードは一見動いているように見えますが、SQLインジェクションの危険があります。
外部入力を文字列連結でそのままSQLに埋め込むと、悪意のある入力によって不正なSQLが実行される可能性があるためです。

こうした問題を防ぐために使うのが、WordPressの $wpdb->prepare() です。
$wpdb->prepare() を正しく使えば、可読性を保ちながら、ユーザー入力を安全にSQLへ渡すことができます。

この記事では、WordPressの $wpdb->prepare() について、基本構文、使い方、プレースホルダーの種類、SELECT/INSERT/UPDATE/DELETEでの実践例、よくある間違い、SQLインジェクション対策の考え方、注意点までできるだけわかりやすく解説します。

「なんとなく書いている」状態から卒業して、安全で保守しやすいWordPressのDB操作ができるようになることを目指します。


$wpdbとは何か

$wpdb は、WordPressが標準で提供しているデータベース操作用のグローバルオブジェクトです。
WordPress本体の投稿、ユーザー、メタ情報なども内部ではデータベースに保存されていますが、カスタム開発ではこの $wpdb を使って直接SQLを実行できます。

たとえば、次のようなことができます。

  • データを検索する

  • レコードを挿入する

  • レコードを更新する

  • レコードを削除する

  • カスタムテーブルを扱う

  • 生SQLで複雑な条件検索を行う

WordPressには WP_Queryget_posts() など便利なAPIもありますが、それらでは対応しづらいケースでは $wpdb が役立ちます。

$wpdb を使う基本形

global $wpdb;

このように global 宣言をしてから使うのが一般的です。


$wpdb->prepare()とは何か

$wpdb->prepare() は、SQL文の中に変数を安全に埋め込むためのメソッドです。

簡単に言うと、次のような役割があります。

  • SQL文の中にユーザー入力を直接書かないようにする

  • データ型に応じた安全な置換を行う

  • SQLインジェクションのリスクを減らす

  • 可読性の高いSQLを書きやすくする

基本イメージ

たとえば、危険な書き方はこうです。

$sql = "SELECT * FROM my_table WHERE id = " . $_GET['id'];

一方、prepare() を使うとこう書けます。

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id = %d",
$_GET['id']
);

この %dプレースホルダーで、あとから安全に値を埋め込むための印です。


なぜ$wpdb->prepare()が必要なのか

WordPressでSQLを安全に扱うためには、外部入力をそのままSQLに入れないことが非常に重要です。
たとえば、検索フォーム、URLパラメータ、管理画面入力、AJAX送信データなど、外部から入ってくる値は常に信用できるとは限りません。

もし入力値を直接SQLへつなげると、悪意のあるデータでクエリが壊されたり、不正に情報を取得されたり、最悪の場合は削除・改ざんされたりする可能性があります。

危険な例

$user_id = $_GET['id'];

$sql = "SELECT * FROM my_table WHERE id = $user_id";

もし id に意図しない文字列が入れば、SQL文そのものが変わるおそれがあります。

安全な例

$user_id = $_GET['id'];

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id = %d",
$user_id
);

このように、SQLの構造と値を分けて書くことで安全性が高まります。


$wpdb->prepare()の基本構文

$wpdb->prepare() の基本形は次のとおりです。

$wpdb->prepare( SQL文, 値1, 値2, ... );

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id = %d AND name = %s",
10,
'田中'
);

この場合、

  • %d には整数が入る

  • %s には文字列が入る

という意味になります。


プレースホルダーの種類

$wpdb->prepare() では、主に次のプレースホルダーを使います。

%d

整数用です。

%d

IDや件数など、整数値を渡すときに使います。

%f

浮動小数点数用です。

%f

小数を含む数値に使います。

%s

文字列用です。

%s

名前、メールアドレス、タイトル、キーワードなどの文字列に使います。


プレースホルダーごとの基本例

整数 %d

$post_id = 15;

$sql = $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID = %d",
$post_id
);

文字列 %s

$user_login = 'admin';

$sql = $wpdb->prepare(
"SELECT * FROM {$wpdb->users} WHERE user_login = %s",
$user_login
);

小数 %f

$price = 19.99;

$sql = $wpdb->prepare(
"SELECT * FROM my_products WHERE price >= %f",
$price
);


SELECT文での使い方

$wpdb->prepare() は、特にSELECT文でよく使われます。
たとえば、特定のIDや名前でデータを検索したいときです。

例1: IDで1件取得する

global $wpdb;

$post_id = 10;

$sql = $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID = %d",
$post_id
);

$result = $wpdb->get_row($sql);

この例では、投稿IDが10のレコードを1件取得しています。

例2: ユーザー名で検索する

global $wpdb;

$user_login = 'sample_user';

$sql = $wpdb->prepare(
"SELECT * FROM {$wpdb->users} WHERE user_login = %s",
$user_login
);

$user = $wpdb->get_row($sql);

例3: 複数条件で検索する

global $wpdb;

$status = 'publish';
$author_id = 3;

$sql = $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_author = %d",
$status,
$author_id
);

$posts = $wpdb->get_results($sql);


LIKE検索での使い方

初心者がよく迷うのが LIKE検索 です。
LIKE を使うときは、ワイルドカード % の扱いに注意する必要があります。

間違いやすい書き方

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE name LIKE '%s'",
$keyword
);

これは意図どおりに動かない場合があります。
LIKE 検索では、ワイルドカードを値側に含めるのが基本です。

正しい考え方

$keyword = 'tanaka';
$like = '%' . $wpdb->esc_like($keyword) . '%';

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE name LIKE %s",
$like
);

なぜ esc_like() を使うのか

LIKE検索では %_ に特別な意味があります。
ユーザー入力にこれらが含まれると、意図しないマッチが起こることがあるため、$wpdb->esc_like() を使って適切にエスケープします。

実用例

global $wpdb;

$keyword = $_GET['keyword'] ?? '';
$like = '%' . $wpdb->esc_like($keyword) . '%';

$sql = $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_title LIKE %s",
$like
);

$results = $wpdb->get_results($sql);


INSERT文ではprepareをどう使うのか

実は、WordPressではINSERTのときに毎回 prepare() を使うより、$wpdb->insert() を使うほうが簡潔で安全です。

推奨される書き方

global $wpdb;

$wpdb->insert(
'my_table',
[
'name' => '田中',
'email' => 'tanaka@example.com',
'age' => 30,
],
[
'%s',
'%s',
'%d',
]
);

この方法なら、値とフォーマットを分けて安全にINSERTできます。

それでも prepare を使う例

生SQLでどうしてもINSERTを書きたい場合は、次のようにできます。

$sql = $wpdb->prepare(
"INSERT INTO my_table (name, email, age) VALUES (%s, %s, %d)",
'田中',
'tanaka@example.com',
30
);

$wpdb->query($sql);

ただし、通常は insert() のほうが読みやすく、ミスも少ないです。


UPDATE文ではprepareをどう使うのか

UPDATEも同様に、通常は $wpdb->update() を使うのが便利です。

推奨される書き方

global $wpdb;

$wpdb->update(
'my_table',
[
'name' => '佐藤',
'age' => 35,
],
[
'id' => 1,
],
[
'%s',
'%d',
],
[
'%d',
]
);

prepare を使うUPDATE例

$sql = $wpdb->prepare(
"UPDATE my_table SET name = %s, age = %d WHERE id = %d",
'佐藤',
35,
1
);

$wpdb->query($sql);

こちらでも動きますが、単純な更新なら update() を使うほうが安全です。


DELETE文ではprepareをどう使うのか

DELETEも、できれば $wpdb->delete() を使うのが一般的です。

推奨される書き方

global $wpdb;

$wpdb->delete(
'my_table',
[
'id' => 1,
],
[
'%d',
]
);

prepare を使うDELETE例

$sql = $wpdb->prepare(
"DELETE FROM my_table WHERE id = %d",
1
);

$wpdb->query($sql);


IN句で複数値を扱う方法

$wpdb->prepare() で少し悩みやすいのが、IN (...) 句です。
たとえば、複数IDを条件に使いたいケースです。

単純にはこう書けない

$ids = [1, 2, 3];

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id IN (%d)",
$ids
);

これは正しく扱えません。
配列をそのまま %d に渡すことはできないからです。

正しい考え方

IDの数だけプレースホルダーを動的に作ります。

global $wpdb;

$ids = [1, 2, 3];
$placeholders = implode(', ', array_fill(0, count($ids), '%d'));

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id IN ($placeholders)",
...$ids
);

$results = $wpdb->get_results($sql);

このようにすれば、配列の件数に応じて %d, %d, %d のような形を動的に作れます。

文字列配列の場合

$statuses = ['publish', 'draft'];
$placeholders = implode(', ', array_fill(0, count($statuses), '%s'));

$sql = $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_status IN ($placeholders)",
...$statuses
);


テーブル名やカラム名にprepareは使えるのか

ここは非常に大事なポイントです。

$wpdb->prepare() は値を安全に埋め込むためのものであり、テーブル名やカラム名の識別子には基本的に使いません。`

やってはいけないイメージ

$table = 'my_table';

$sql = $wpdb->prepare(
"SELECT * FROM %s WHERE id = %d",
$table,
1
);

これは期待どおりには使えません。
%s は文字列として引用されるため、テーブル名としては不適切です。

どうすればよいか

テーブル名やカラム名は、あらかじめ固定値として管理するのが基本です。

$table_name = $wpdb->prefix . 'my_table';

$sql = $wpdb->prepare(
"SELECT * FROM {$table_name} WHERE id = %d",
1
);

ただし、このときも $table_name が外部入力から作られていないことが重要です。
テーブル名やカラム名をユーザー入力で切り替える設計は、原則として避けたほうが安全です。


prepareしただけで実行されるわけではない

$wpdb->prepare() は、あくまで安全なSQL文字列を作るメソッドです。
それ自体はクエリを実行しません。

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id = %d",
1
);

この時点ではまだ実行されていません。
その後に get_row()get_results()query() などへ渡す必要があります。

$result = $wpdb->get_row($sql);

よく使うwpdbメソッドとの組み合わせ

$wpdb->prepare() は、以下のようなメソッドと組み合わせて使うことが多いです。

get_var()

1つの値だけ取得したいとき

$count = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM my_table WHERE status = %s",
'active'
)
);

get_row()

1行だけ取得したいとき

$user = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM my_table WHERE id = %d",
1
)
);

get_results()

複数行取得したいとき

$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM my_table WHERE category = %s",
'news'
)
);

query()

INSERT、UPDATE、DELETE など、取得以外の実行時に使うことが多い

$wpdb->query(
$wpdb->prepare(
"DELETE FROM my_table WHERE id = %d",
5
)
);

SQLインジェクションとは何か

$wpdb->prepare() の重要性を理解するためには、SQLインジェクションを知っておく必要があります。

SQLインジェクションとは、悪意のある入力によってSQL文を改変し、本来意図していない操作を実行させる攻撃です。

たとえば、外部入力をそのままSQLにつなげていると、次のような問題が起きる可能性があります。

  • 他人のデータを不正に取得される

  • ログイン回避される

  • レコードを削除される

  • データを改ざんされる

なぜ起きるのか

原因はシンプルで、SQL文の構造と値が混ざっているからです。
prepare() を使えば、この構造と値を分けて扱えるため、危険性を大きく減らせます。


よくある間違いと注意点

ここからは、初心者がやりがちな間違いを整理します。


1. 文字列連結でSQLを書く

危険な例

$sql = "SELECT * FROM my_table WHERE name = '" . $_POST['name'] . "'";

外部入力をそのままSQLへ入れているので危険です。

安全な例

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE name = %s",
$_POST['name']
);

2. プレースホルダーの数と値の数が合っていない

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id = %d AND name = %s",
1
);

プレースホルダーは2つあるのに、値が1つしかありません。
これでは正しく動きません。

正しくは

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id = %d AND name = %s",
1,
'田中'
);

3. データ型に合わないプレースホルダーを使う

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id = %s",
10
);

これでも動く場合はありますが、IDなら %d を使うべきです。
型に合ったプレースホルダーを使うことで、意図が明確になり、安全性も高まります。


4. LIKE句で esc_like() を使わない

LIKE検索は特にミスが多い部分です。
単に % をくっつけるだけでなく、入力値は esc_like() で処理するのが安全です。


5. prepareすれば何でも安全だと思い込む

$wpdb->prepare() は非常に重要ですが、万能ではありません。
特に次の点は別で考える必要があります。

  • テーブル名・カラム名の動的組み立て

  • 権限チェック

  • nonce検証

  • バリデーション

  • サニタイズ

  • 出力時のエスケープ

つまり、DBクエリが安全でも、アプリ全体が安全とは限りません。


6. prepareを忘れて insert / update / delete で query を使う

手書きSQLで query() を使うときは、必ず prepare() を挟む意識が重要です。
単純な処理なら、insert()update()delete() を使う方がミスしにくいです。


実務でおすすめの書き方

WordPressでDB操作をする際は、次のように考えると整理しやすいです。

SELECT系

複雑な条件検索やJOINが必要なら prepare() + get_results() / get_row() を使う

INSERT系

基本は insert() を使う

UPDATE系

基本は update() を使う

DELETE系

基本は delete() を使う

LIKE検索

esc_like() + prepare() を組み合わせる

この方針だと、無理に全部を生SQLで書かずに済むため、安全で保守しやすいコードになりやすいです。


実践例1: カスタムテーブルから1件取得する

global $wpdb;

$table_name = $wpdb->prefix . 'members';
$member_id = 7;

$sql = $wpdb->prepare(
"SELECT * FROM {$table_name} WHERE id = %d",
$member_id
);

$member = $wpdb->get_row($sql);

ポイント

  • テーブル名は固定で組み立てる

  • 値だけを %d で渡す

  • 実行は get_row() で行う


実践例2: 管理画面の検索キーワードで一覧を絞り込む

global $wpdb;

$table_name = $wpdb->prefix . 'members';
$keyword = $_GET['keyword'] ?? '';
$like = '%' . $wpdb->esc_like($keyword) . '%';

$sql = $wpdb->prepare(
"SELECT * FROM {$table_name} WHERE name LIKE %s OR email LIKE %s",
$like,
$like
);

$members = $wpdb->get_results($sql);

ポイント

  • $_GET の値を直接連結しない

  • esc_like() でLIKE用に整える

  • 複数条件でも prepare() を使う


実践例3: 投稿ステータスと作成者で絞り込む

global $wpdb;

$status = 'publish';
$author_id = 2;

$sql = $wpdb->prepare(
"SELECT ID, post_title FROM {$wpdb->posts} WHERE post_status = %s AND post_author = %d",
$status,
$author_id
);

$posts = $wpdb->get_results($sql);


実践例4: 生SQLでUPDATEする

global $wpdb;

$table_name = $wpdb->prefix . 'members';
$name = '山田';
$age = 28;
$id = 3;

$sql = $wpdb->prepare(
"UPDATE {$table_name} SET name = %s, age = %d WHERE id = %d",
$name,
$age,
$id
);

$wpdb->query($sql);

ただし、単純な更新なら update() のほうがよいです。


実践例5: 複数IDをIN句で検索する

global $wpdb;

$table_name = $wpdb->prefix . 'members';
$ids = [2, 4, 6];

$placeholders = implode(', ', array_fill(0, count($ids), '%d'));

$sql = $wpdb->prepare(
"SELECT * FROM {$table_name} WHERE id IN ($placeholders)",
...$ids
);

$members = $wpdb->get_results($sql);


デバッグ時に確認したいポイント

$wpdb->prepare() を使っているのにうまく動かない場合は、次の点を確認すると原因を切り分けやすいです。

1. SQL文字列を確認する

error_log($sql);

実際にどんなSQLになっているかを見ると、プレースホルダーのミスに気づきやすくなります。

2. $wpdb->last_error を確認する

if ( ! empty($wpdb->last_error) ) {
error_log($wpdb->last_error);
}

SQL文の構文エラーやテーブル名ミスがわかることがあります。

3. プレースホルダーと値の数を確認する

数が合っていないと、不正なクエリになりやすいです。

4. テーブル名が正しいか確認する

特に $wpdb->prefix を使う場合、マルチサイトや接頭辞変更で意図と違う名前になることもあります。


prepareとsanitizeの違い

ここも初心者が混同しやすいポイントです。

prepare

SQLへ値を安全に埋め込むためのもの

sanitize

入力値自体を整形・検証するもの

たとえば、メールアドレスやテキスト入力は、SQLへ渡す前にサニタイズすることがあります。

$name = sanitize_text_field($_POST['name']);

そのうえで、SQLには prepare() で渡します。

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE name = %s",
$name
);

つまり、役割は別です。

  • sanitize: 値を整える

  • prepare: SQLへ安全に渡す

両方必要なことが多いです。


prepareとesc_sqlの違い

WordPressには esc_sql() もあります。
ただし、通常の値埋め込みでは $wpdb->prepare() を優先するのが基本です。

esc_sql()

SQL用に文字列をエスケープするための関数

prepare()

SQL文の中へ値を型付きで安全に埋め込む

値の埋め込みがあるなら、prepare() のほうが意図が明確で、安全性も高いです。
esc_sql() は限定的な用途で使われますが、初心者はまず prepare() を中心に覚えるのがわかりやすいです。


初心者におすすめの覚え方

最初は難しく見えるかもしれませんが、次の3つを覚えるだけでもかなり変わります。

1. 外部入力をSQLに直接つなげない

$_GET$_POST、AJAX値を文字列連結しない

2. 値を入れるところは %d / %s / %f を使う

整数なら %d、文字列なら %s

3. LIKE検索だけは esc_like() をセットで使う

ここは特にミスしやすいので意識する


よくある質問

WordPressの$wpdb->prepare()は何のために使いますか?

SQL文の中に値を安全に埋め込むために使います。
主な目的は、SQLインジェクションのリスクを減らし、安全なDB操作を行うことです。

%d と %s はどう使い分けますか?

%d は整数、%s は文字列、%f は小数に使います。
値の型に合わせて使うことで、安全でわかりやすいSQLになります。

LIKE検索でもprepareは使えますか?

使えます。
ただし、ワイルドカードを値側で付け、さらに $wpdb->esc_like() を使うのが基本です。

テーブル名にもprepareを使えますか?

基本的には使いません。
prepare() は値の埋め込み用であり、テーブル名やカラム名の識別子には不向きです。
テーブル名は固定で組み立てるようにします。

INSERTやUPDATEでもprepareを使うべきですか?

生SQLを書くなら使うべきです。
ただし、単純なINSERTやUPDATE、DELETEなら $wpdb->insert()$wpdb->update()$wpdb->delete() のほうが安全で書きやすいです。


まとめ

WordPressでデータベースを安全に扱ううえで、$wpdb->prepare() は非常に重要な基本技術です。
特に、検索フォーム、URLパラメータ、管理画面入力、AJAXリクエストなど、外部入力が絡むSQLでは必須に近い考え方です。

ポイントを整理すると次のとおりです。

  • $wpdb->prepare() はSQL文に値を安全に埋め込むためのメソッド

  • SQLインジェクション対策として重要

  • %d%s%f を使い分ける

  • LIKE検索では esc_like() を併用する

  • テーブル名やカラム名には基本的に使わない

  • SELECTでは prepare() を使う場面が多い

  • INSERT / UPDATE / DELETE は専用メソッドも活用すると安全

  • prepareだけでなく、sanitize、権限チェック、nonceも重要

WordPressのカスタム開発では、動くコードを書くこと以上に、安全で保守しやすいコードを書くことが重要です。
$wpdb->prepare() を正しく使えるようになると、プラグイン開発や管理画面開発、カスタムテーブル設計の品質が一段上がります。

初心者の方は、まず次の型をしっかり覚えるところから始めるのがおすすめです。

$sql = $wpdb->prepare(
"SELECT * FROM my_table WHERE id = %d AND name = %s",
$id,
$name
);

この基本が身につけば、より複雑なクエリにも安全に対応しやすくなります。

Si quieres conocer otros artículos parecidos a WordPressの$wpdb->prepareの使い方|SQLインジェクションを防ぐ安全な書き方 puedes visitar la categoría Sisutemuunyo.

Go up