【WordPress】WordPress から DB を操作する

今回は WordPress からDB(データベース)にアクセスして UPDATE と INSERT 処理を行う方法をご紹介します。
また UPSERT 処理(UPDATE + INSERT)を行う方法についても合わせてご紹介します。

## 環境
WordPress： 4.9.19
PHP：7.0.27
MySQL：5.6.38

## DB設計
今回扱うサンプルのテーブルを作成します。
このテーブルでは学生の情報(名前、メールアドレス、学年)を管理することとします。
```session_id``` は UPDATE 処理を行う際にキーとして用います。

```
CREATE TABLE wp_test (
    id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
    name VARCHAR(30) NOT NULL COMMENT '名前',
    mail VARCHAR(30) NOT NULL COMMENT 'メールアドレス',
    grade TINYINT(3) UNSIGNED DEFAULT NULL COMMENT '学年',
    session_id VARCHAR(100) NOT NULL COMMENT '更新用のセッションID',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '登録日時',
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日時',
    PRIMARY KEY(id),
    KEY `idx_ws_01` (`session_id`),
    UNIQUE KEY `uk_ws_01` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='テストテーブル';
```

## DBアクセス

WordPress には ```wpdb``` というクラスが用意されており、ここに含まれる関数で DB を操作することができます。
このクラスは直接呼び出すことはできないので以下のように ```$wpdb``` というグローバル変数を用いてアクセスします。

``` function.php
global $wpdb;
```

## INSERT
新たにデータを挿入する場合は ```insert``` 関数を用います。
使い方は以下の通りです。

構文：```$wpdb->insert( $table, $data, $format );```

```$table``` ：テーブル名（文字列）
```$data```：挿入するデータ(配列)
```$format```：挿入するデータのフォーマット(配列)

以下の例はユーザーが名前、メールアドレス、学年を入力し、```session_id``` とともに送信してテーブルに保存するものです。
サーバー側で受け取ったデータを ```$PostArray``` 変数に格納し,
ここから一つずつ取り出してテーブル挿入用の配列にセットして ```insert``` します。

``` function.php

// テーブルに挿入
$wpdb->insert(
  'wp_test', 
  array(
    'name' => $PostArray['name'],
    'mail' => $PostArray['mail'],
    'grade' => $PostArray['grade'],
    'session_id' => $PostArray['session_id']
  ),
  array('%s', '%s', '%d', '%s')
);
```

第2引数の配列は「テーブルのカラム名 => 挿入するデータ」の形式でカラムごとに配列の要素を書いていきます。

第3引数の配列には第2引数に対応する形で各データのフォーマットを指定します。

例えば配列の1番目には ```name``` カラムに対応する ```%s``` (文字列)を指定し、3番目には ```grade``` カラムに対応する ```%d```(整数) を指定しています。

なおフォーマット形式は以下の3つの中から指定します。

- ```%s```：文字列
- ```%d```：整数
- ```%f```：浮動小数点数

ちなみに省略すると挿入するデータはすべて文字列として扱われます。

また、テーブルのカラム名と挿入するデータは両方とも SQL エスケープしてはいけません。
これは ```insert``` 関数内部でエスケープ処理が行われるためです。
``` insert ``` 関数の内部では ```_insert_replace_helper``` という名前の関数が実行され、さらにその中では``` prepare ``` 関数によってエスケープ処理が施されたうえで INSERT 文が実行されるようになっています。
```_insert_replace_helper``` 関数のソースコードはこちらをご覧ください。
https://developer.wordpress.org/reference/classes/wpdb/_insert_replace_helper/

## UPDATE
既存のデータを更新するには ```update``` 関数を使います。
使い方は以下の通りです。

構文：```$wpdb->update( $table, $data, $where, $format = null, $where_format = null );```

```$table``` ：テーブル名（文字列）
```$data```：更新後のデータ(配列)
```$where```：WHERE句に使うカラムと値(配列)
```$format```：更新後のデータのフォーマット(配列)
```$where_format```：```$where``` に使う値のフォーマット(配列)

第2引数の ```$data``` には INSERT の場合と同じように「テーブルのカラム名 => 更新するデータ」の形式で配列を作成して渡します。

第3引数の ```$where``` には更新するデータの条件をキーと値のペア(配列形式)で指定します。
これは SQL の WHERE 句の条件文に該当する部分です。
複数個指定すると AND でつながれます。

第5引数の ```$where_format``` は ```$where``` で指定した値のフォーマットを指定します。
省略した場合はすべて文字列として扱われます。

また、```$data``` と ```$where``` に渡すカラム名とデータは両方とも事前にエスケープ処理してはいけません。

以下は ```session_id``` の値を参照してデータを更新する例です。

``` function.php

// データを更新
$wpdb->update(
  'wp_test',
  array(
    'name' => $PostArray['name'],
    'mail' => $PostArray['mail'],
    'grade' => $PostArray['grade'],
    'session_id' => $PostArray['session_id']
  ),
  array(
    'session_id' => $PostArray['session_id']
  ),
  array('%s', '%s', '%d', '%s'),
  array('%s')
);

```
第3引数のキーに ```session_id``` を、値に ```$PostArray['session_id']``` を指定することで、送信された ```session_id``` と同じ ```session_id``` を持つデータ(行)を更新しています。

## UPSERT
最後に UPSERT を行う方法についてご紹介します。
UPSERT とは特定の値を持つデータ(行)がテーブルにあれば UPDATE し、なければ INSERT する処理のことです。
今回は送信された ```session_id``` がテーブルにあるか否かを確認し、結果に応じて INSERT および UPDATE を実行します。

特定の値の有無を確認するために今回は ```get_var``` 関数を用いています。
```get_var``` 関数の使い方は以下の通りです。

構文：```$wpdb->get_var( $query, $column_offset, $row_offset ); ```

```$query``` ：実行したい SQL(文字列) 
```$column_offset```：何列目の値を返すか(一つ目は0、初期値は0)(整数) 
```$row_offset```：何行目の値を返すか(一つ目は0、初期値は0)(整数) 

この関数は第1引数の ```$query``` で記述した SQL の実行結果を一つだけ返却します。
もし実行結果に該当する値がなければ ```NULL``` が返されます。
また、通常 SQL は複数の行と列に渡る値を返却しますので ```$column_offset``` と ```$row_offset``` で何行何列の値を返すかを指定します。
何も指定しなければ先頭の値(0行0列)が返却されます。
そのため今回のように一意性制約のある値を取得する場合は無指定で問題ないと思います。

以下は UPSERT 処理の例です。

``` function.php
// 送信されたsession_idと同じ値を持つ行のidを取得
$targetId = $wpdb->get_var($wpdb->prepare("SELECT id FROM wp_test WHERE session_id = %s", $PostArray["session_id"]));

if (is_null($targetId)) {
  // targetIdがNULLならデータを追加
 $wpdb->insert(
    'wp_test', 
    array(
      'name' => $PostArray['name'],
      'mail' => $PostArray['mail'],
      'grade' => $PostArray['grade'],
      'session_id' => $PostArray['session_id']
    ),
    array('%s', '%s', '%d', '%s')
  );
} else {
  // targetId が NULL でなければデータを更新
  $wpdb->update(
    'wp_test',
    array(
      'name' => $PostArray['name'],
      'mail' => $PostArray['mail'],
      'grade' => $PostArray['grade'],
      'session_id' => $PostArray['session_id']
    ),
    array(
      'session_id' => $PostArray['session_id']
    ),
    array('%s', '%s', '%d', '%s'),
    array('%s')
  );
}
```
　
    ```get_var```関数の```$query```にはSQLインジェクション対策として ```prepare``` 関数を用いています。
　
    ```prepare``` 関数の使い方はこちらを参考にしてください。
[関数リファレンス/wpdb Class - WordPress Codex 日本語版](https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/wpdb_Class#SQL_.E3.82.A4.E3.83.B3.E3.82.B8.E3.82.A7.E3.82.AF.E3.82.B7.E3.83.A7.E3.83.B3.E6.94.BB.E6.92.83.E3.81.8B.E3.82.89.E3.82.AF.E3.82.A8.E3.83.AA.E3.82.92.E4.BF.9D.E8.AD.B7.E3.81.99.E3.82.8B)
　
    ```get_var```関数は SQL によって何も取得できなければ```NULL```が返却されますので、結果が ```NULL``` だった場合は INSERT を、 ```NULL``` ではなかった場合は UPDATE を実行するようにしています。
　
UPSERT処理は以上です。

最後までお読みいただきまして、ありがとうございました。

## 参考
[関数リファレンス/wpdb Class - WordPress Codex 日本語版](https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/wpdb_Class)
[wpdb::_insert_replace_helper() \| Method | WordPress Developer Resources](https://developer.wordpress.org/reference/classes/wpdb/_insert_replace_helper/)