どうも。独学副業プログラマーのパチです。
ワードプレスのプラグインを開発していると、大抵の場合ワードプレスのデータベース操作が必要になるのですが「挿入、更新、取得、削除」などの操作が発生しますよね。
ワードプレスでは『wpdbクラス』という、データベースの操作をすることができるのですが、少し使い勝手が悪いと感じました😅
そこで、SQLインジェクション対策をしつつ、何度も使い回しやすいテクニックを紹介したいと思います。
WPのSQLインジェクション対策!この部分が悩ましい!
あるテーブルに対して、IDが一致する行を取得する場合を例に挙げて紹介します。
※AAARepositoryはデータベースとのやりとりをするためのクラスです。
/*SQLインジェクション対策「前」*/
class AAARepository
{
public function get(int $id)
{
global $wpdb;
$res = $wpdb->get_row("SELECT * FROM [テーブル名] WHERE id=".$id);
return $res;
}
}
↓対策後
/*SQLインジェクション対策「後」*/
class AAARepository
{
public function get(int $id)
{
global $wpdb;
$sql = $wpdb->prepare("SELECT * FROM [テーブル名] WHERE id=%d", $id);
$res = $wpdb->get_row($sql);
return $res;
}
}
赤線の「prepare」を使用することでSQLインジェクション対策することができました。
しかし、
一箇所のみの使用ならこれでも良いですが、似たようなSQL文を他の箇所でもたくさん出現するので、使いまわしたいなーって思うケースがほとんどだと思います。
prepareを使い回しできるようにする
ということで、SQLインジェクション対策部分を使いまわせるようにするのが、この記事のテーマになります。
結論としては、『使いまわせる親クラスを作成する。』という内容なのですが、「なんのこっちゃわからん」という人に向けて段階を踏んで丁寧に解説していきまっせ😄
prepareを別メソッドにする
まずは「prepare」の部分を、別のメソッドとして記述します。
/*prepareの部分を別のメソッドにする*/
class AAARepository
{
public function get(int $id)
{
global $wpdb;
$res = $wpdb->get_row($this->getSelectSql($id)); //メソッドの呼び出し
return $res;
}
/*prepareの部分はこちらのメソッドに記述*/
private function getSelectSql($id)
{
global $wpdb;
/*変数の型を判別*/
$placeholder = '';
if(gettype($id) == 'integer'){
$placeholder = '%d';
}else if(gettype($id) == 'float'){
$placeholder = '%f';
}else{
$placeholder = '%s';
}
/*prepareでSQL文を作って返す*/
return $wpdb->prepare("SELECT * FROM [テーブル名] WHERE id=".$placeholder, $id);
}
}
「getSelectSql」というメソッド記述しました。同様にidに一致するSELECT文が取得できますね。
変数の型を判別しているので、条件である$idが、整数、浮動小数店、文字列であろうと、この関数で一括して対応することができます。
しかし、現状では「id」というカラムでしか検索できません。(※ getSelectSqlの赤線部分に注目)
例えば、「name」や「address」といったカラムで検索したい時には使いまわせない!これは問題です🤣
指定のカラム名にも使えるようにする
別のカラム名でも使えるように、下記のように書き換えました。
class AAARepositiory
{
public function get(int $id)
{
global $wpdb;
/*カラム名をkeyとした配列を作る*/
$element = array(
'id' => $id
);
$res = $wpdb->get_row($this->getSelectSql($element));
return $res;
}
private function getSelectSql($element)
{
global $wpdb;
$placeholder = '';
if(gettype($element) == 'integer'){
$placeholder = '%d';
}else if(gettype($element) == 'float'){
$placeholder = '%f';
}else{
$placeholder = '%s';
}
/*配列のkeyを取得*/
$keys = array_keys($element);
return $wpdb->prepare("SELECT * FROM [テーブル名] WHERE ".$keys[0]."=".$placeholder, $element);
}
}
getメソッドで代入する際に、key名をカラム名にした配列を作って引数として渡します。
こうすれば、カラム名を変えても問題なく使用できます。
※引数にする配列を記述する場所は考えた方が良さそうです。
別のテーブルでも使い回しできるようにする
getSelectSqlが使えるようになりましたが、今のままでは「AAARepository内」でしか使えません。
そこで、親クラスBaseRepositoryを作ってAAARepositoryに継承させる形にします。
class BaseRepository
{
protected $db;
protected $table;
protected function getSelectSql($element)
{
$keys = array_keys($element); //配列のkeyを取得
$placeholder = $this->judgeType($element[$keys[0]]); //keyに該当する型を取得
return $this->db->prepare("SELECT * FROM ".$this->table . " WHERE ".$keys[0]."=".$placeholder, $element);
}
/*型判定のメソッド*/
private function judgeType($type)
{
if(gettype($type) == 'integer'){
return '%d';
}else if(gettype($type) == 'float'){
return '%f';
}else{
return '%s';
}
}
}
↑こんな感じで親クラスを作って
↓こんな感じで継承させる
class AAAReposiotry extends BaseRepository
{
public function __construct()
{
global $wpdb;
$this->db = $wpdb;
$this->table = [テーブル名];
}
public function get(int $id)
{
$element = array(
'id' => $id
);
$res = $this->db->get_row($this->getSelectSql($element));
return $res;
}
}
これでAAARepositoryでgetSelectSqlメソッドが使えるのはもちろん、BBBRepositoryやCCCRepositoryなどを作った時もBaseRepositoryを継承させるだけで簡単に流用できます。
今回は、SQLインジェクション対策をした上で「 [カラム名] = 〇〇」という条件でデータベースからデータを取得することができました。
この時点でお気づきの方もいらっしゃるでしょうが、BaseRepositoryにgetSelectSqlのようなメソッドを記述するとすぐに流用できます。
なので実際に僕が使用しているBaseRepositoryは、テーブルを結合したり、値の合計をとってきたりなどのメソッドも実装されています。

ご自身に必要なメソッドを書いていきましょう!
まとめ
ワードプレスのデータベースにアクセスする際に、SQLインジェクション対策したSQL文を繰り返し簡単に発行できるプログラムを紹介しました。
正直、初心者にとっては難しいと感じるレベルかと思います😅
同じようなことを繰り返す部分は共通化しておいた方がバグの発見も早くなりますし、何度もワードプレスのプラグイン開発をするなら開発効率はかなりUPします😚
また本当はAAARepositoryに書いてある以下の部分
$element = array('id' => $id);
これはリポジトリではなく、ドメイン知識に当たる部分なのでドメインに書くべきだと思いますが、今回は例では簡略化してリポジトリに記述しています。
この部分は『ドメイン駆動設計(DDD)』という考えのお話になるのですが、初心者が一発で理解できる内容ではありません😅
より深く勉強されたい方は『ドメイン駆動設計入門』が非常に参考になると思います。
コメント