MySQL関数からMySQLi関数への移行作業メモ


はじめに
--------

PHPでMySQL関数を利用している場合、早急にMySQLi関数またはPDOへ移行を行う必要がある。
MySQLへの依存を取り除くためにもPDOへの移行が望ましいが、当面MySQLからの移行がないと言い切れる場合、MySQLi関数の手続き型表現を利用することにより、PDOへの乗り換えを行うよりも手間をかけずに移行することが出来る。
繰り返すが、可能ならばPDOへの移行が望ましいため、客先がPDOへの移行にコストがかかるという理由で渋っている場合にのみ参考とすること。

なお、参考コードは全てPHP 5.1系、5.2系、5.4系で動作確認をしている。
世の中には（イントラネット内とはいえ）未だにPHP 5.1系で稼動しているという恐ろしい現場があることを忘れてはならない...。

注意事項
--------

### 関数の引数順序について

MySQLi関数では全ての関数においてMySQLi接続オブジェクト、または結果オブジェクトを引数に渡す必要がある。
MySQL関数では接続リソース、または結果リソースは省略可能な関数があり、また、省略可能な場合引数の順序が第2引数以降となっている。
そのため、MySQL関数とMySQLi関数では引数の順序が異なっているものが多々存在する。

#### 接続リソース、または結果リソースが省略可能、または指定不要な関数一覧

* mysql_affected_rows
* mysql_client_encoding
* mysql_close
* mysql_db_query
* mysql_errno
* mysql_error
* mysql_escape_string
* mysql_get_client_info
* mysql_get_host_info
* mysql_get_proto_info
* mysql_get_server_info
* mysql_info
* mysql_insert_id
* mysql_ping
* mysql_query
* mysql_real_escape_string
* mysql_select_db
* mysql_set_charset
* mysql_stat
* mysql_thread_id
* mysql_unbuffered_query
* mysql_create_db
* mysql_drop_db
* mysql_list_dbs
* mysql_list_fields
* mysql_list_processes
* mysql_list_tables

#### 引数の順序が異なる関数一覧

* mysql_connect
* mysql_escape_string
* mysql_query
* mysql_real_escape_string
* mysql_select_db
* mysql_set_charset

### 接続可否判定

`mysql_connect()`では接続に失敗すると真偽値`false`が返っていていたが、`mysqli_connect()`では接続に失敗しても`mysqli`オブジェクトが返る。
そのため、接続に成功したかを確認するには `mysqli_connect_error()` 関数の返り値が空文字か、`mysqli_connect_errno()` の返り値が整数「0」以外かの判定を行う必要がある。

#### 実装例

```php
$conn = mysqli_connect('host', 'user', 'password', 'dbname');
if(mysqli_connect_errno() > 0){
  echo "接続に失敗しました";
  exit;
}
```

### 結果の取得について

`mysql_fetch_row()`等の結果を取得する関数において、MySQL関数では取得できる行がない場合は真偽値`false`を返すが、MySQLi関数では`NULL`を返す。
そのため、真偽値`false`であることを厳格に判定している場合、処理を書き換える必要がある。
厳格に判定していない場合は関数の置換で対応可能である。

関数対応表
----------

PHPオンラインマニュアルの該当関数ページにも代替関数の情報が記載されているが、 `mysql_fetch_field()` のように代替することができない関数が記載されていることがあった。
以下筆者が実際に移行作業を行う際に利用した関数を対応表として記す。

|       MySQL関数          |         MySQLi関数        |                                  備考                                       |
|:-------------------------|:--------------------------|:----------------------------------------------------------------------------|
| mysql_affected_rows      | mysqli_affected_rows      |                                                                             |
| mysql_client_encoding    | mysqli_character_set_name |                                                                             |
| mysql_close              | mysqli_close              |                                                                             |
| mysql_connect            | mysqli_connect            |                                                                             |
| mysql_data_seek          | mysqli_data_seek          |                                                                             |
| mysql_db_query           | -                         | `mysqli_select_db()` と `mysqli_query()` の組み合わせで代用可                       |
| mysql_errno              | mysqli_errno              |                                                                             |
| mysql_error              | mysqli_error              |                                                                             |
| mysql_escape_string      | mysqli_real_escape_string |                                                                             |
| mysql_fetch_array        | mysqli_fetch_array        |                                                                             |
| mysql_fetch_assoc        | mysqli_fetch_assoc        |                                                                             |
| mysql_fetch_object       | mysqli_fetch_object       |                                                                             |
| mysql_fetch_row          | mysqli_fetch_row          |                                                                             |
| mysql_fetch_field        | mysqli_fetch_field_direct | 返り値のオブジェクトプロパティが異なるため要変換                            |
| mysql_fetch_lengths      | mysqli_fetch_length       |                                                                             |
| mysql_field_flags        | -                         | `mysqli_fetch_field_direct()` にて取得するオブジェクトで代用可                  |
| mysql_field_len          | -                         | `mysqli_fetch_field_direct()` にて取得するオブジェクトで代用可                  |
| mysql_field_name         | -                         | `mysqli_fetch_field_direct()` にて取得するオブジェクトで代用可                  |
| mysql_field_table        | -                         | `mysqli_fetch_field_direct()` にて取得するオブジェクトで代用可                  |
| mysql_field_type         | -                         | `mysqli_fetch_field_direct()` にて取得するオブジェクトで代用可                  |
| mysql_field_seek         | mysqli_field_seek         |                                                                             |
| mysql_free_result        | mysqli_free_result        |                                                                             |
| mysql_get_client_info    | mysqli_get_client_info    |                                                                             |
| mysql_get_host_info      | mysqli_get_host_info      |                                                                             |
| mysql_get_proto_info     | mysqli_get_proto_info     |                                                                             |
| mysql_get_server_info    | mysqli_get_server_info    |                                                                             |
| mysql_info               | mysqli_info               |                                                                             |
| mysql_insert_id          | mysqli_insert_id          |                                                                             |
| mysql_num_fields         | mysqli_field_count        |                                                                             |
| mysql_num_rows           | mysqli_num_rows           |                                                                             |
| mysql_pconnect           | mysqli_connect            | PHP 5.3 以降にてホストプレフィックス「p:」を付与。 PHP 5.2以下では不可      |
| mysql_ping               | mysqli_ping               |                                                                             |
| mysql_query              | mysqli_query              |                                                                             |
| mysql_real_escape_string | mysqli_real_escape_string |                                                                             |
| mysql_result             | -                         | `mysqli_data_seek()`, `mysqli_field_seek()`, `mysqli_fetch_row()`等の組み合わせで代用可 |
| mysql_select_db          | mysqli_select_db          |                                                                             |
| mysql_set_charset        | mysqli_set_charset        |                                                                             |
| mysql_stat               | mysqli_stat               |                                                                             |
| mysql_thread_id          | mysqli_thread_id          |                                                                             |
| mysql_unbuffered_query   | -                         | `mysqli_real_query()` と `mysqli_use_result()` で代用可                             |
| mysql_create_db          | -                         | `mysqli_query()` にて `CREATE DATABASE` クエリ実行で代用可                        |
| mysql_drop_db            | -                         | `mysqli_query()` にて `DROP DATABASE` クエリ実行で代用可                          |
| mysql_db_name            | -                         | `mysqli_query()` にて `SHOW DATABASE` クエリ実行で代用可                          |
| mysql_list_dbs           | -                         | `mysqli_query()` にて `SHOW DATABASE` クエリ実行で代用可                          |
| mysql_tablename          | -                         | `mysqli_query()` にて `SHOW TABLES` クエリ実行で代用可                            |
| mysql_list_fields        | -                         | `mysqli_query()` にて `SHOW COLUMNS` クエリ実行で代用可                           |
| mysql_list_processes     | -                         | `mysqli_query()` にて `SHOW PROCESSLIST` クエリ実行で代用可                       |
| mysql_list_tables        | -                         | `mysqli_query()` にて `SHOW TABLES` クエリ実行で代用可                            |

代替手段
--------

ここに記す代替手段は、あくまで筆者の環境で動いたものである。
これらの代替手段で何かしら致命的な不具合が発生したとしても、筆者は責任を負いかねるためご了承いただきたい。

### mysql_db_query()

```php
mysqli_select_db($conn, $dbname);
$res = mysqli_query($conn, $query);
```

### mysql_fetch_field()

`mysqli_fetch_field_direct()` は厳密には `mysql_fetch_field()` の代替とすることができない。

* `mysql_fetch_field()` はフラグをそれぞれプロパティとして持っているが、 `mysqli_fetch_field_direct()` は `flags` プロパティにビット情報を整数として持っている
* `mysql_fetch_field()` は型情報を文字列として持っているが、 `mysqli_fetch_field_direct()` はビット情報を整数として持っている

ビット情報の整数から文字列に変換するためには`MYSQLI`定数を参照する必要があるが、完全に互換性を保つためにはいくつか手を入れる必要がある。
下記のような実装を用いることにより `mysql_fetch_field()` が返すオブジェクトと完全互換のオブジェクトに変換することが可能となる。

```php
/**
 * MYSQLI_*_FLAG定数を読み込みビットフラグ整数と名称の配列を生成する。
 * 引数に true を渡すと旧MySQL関数互換の配列を返す。
 *
 * @param bool MySQL関数互換フラグ
 * @return array
 */
function get_flag_names($compat = false){
  if($compat){
    return array(
      1 => "not_null",
      2 => "primary_key",
      4 => "unique_key",
      8 => "multiple_key",
      16 => "blob",
      32 => "unsigned",
      64 => "zerofill",
      128 => "binary",
      256 => "blob",
      512 => "auto_increment",
      1024 => "timestamp"
    );
  } else {
    $flags = array();
    $constants = get_defined_constants(true);
    foreach ($constants['mysqli'] as $c => $n){
      if (preg_match('/MYSQLI_(.*)_FLAG$/', $c, $m)){
        if (!array_key_exists($n, $flags)){
          $flags[$n] = strtolower($m[1]);
        }
      }
    }
    return $flags;
  }
}

/**
 * MYSQLI_TYPE_*定数を読み込みビットフラグ整数と名称の配列を生成する。
 * 引数に true を渡すと旧MySQL関数互換の配列を返す。
 *
 * @param bool MySQL関数互換フラグ
 * @return array
 */
function get_type_names($compat = false){
  $types = array();
  $constants = get_defined_constants(true);
  foreach ($constants['mysqli'] as $c => $n){
    if (preg_match('/MYSQLI_TYPE_(.*)$/', $c, $m)){
      $types[$n] = strtolower($m[1]);
    }
  }
  // MySQL関数互換モードの場合、MySQL関数が返す値で置き換える
  if($compat){
    $types[0] = "real";
    $types[1] = "char";
    $types[2] = "int";
    $types[3] = "int";
    $types[4] = "real";
    $types[5] = "real";
    $types[8] = "int";
    $types[9] = "int";
    $types[248] = "string";
    $types[249] = "blob";
    $types[250] = "blob";
    $types[251] = "blob";
    $types[253] = "string";
  }
  return $types;
}

/**
 * mysqli_fetch_field()等で取得したstdClassオブジェクトをmysql_fetch_field()互換の形式で作り直す。
 *
 * @param object mysqli_fetch_field()等で取得したstdClassオブジェクト
 * @param bool MySQL関数互換フラグ
 * @return object
 */
function remake_field_object($field, $compat = true){
  $obj = new stdClass();
  $flags = get_flag_names($compat);
  $types = get_type_names($compat);
  $numeric_types = array(1, 2, 3, 4, 5, 8, 9, 16, 246);
  $compat_flags  = array("not_null", "primary_key", "unique_key", "multiple_key", "blob", "unsigned", "zerofill");
  $nocompat_keys = array("orgname", "orgtable", "db", "catalog", "length", "charsetnr", "decimals");
  $compat_sortkey = array("name", "table", "def", "max_length", "not_null", "primary_key", "multiple_key", "unique_key", "numeric", "blob", "type", "unsigned", "zerofill");
  foreach((array) $field as $key => $value){
    switch($key){
    case "flags":
      foreach($flags as $num => $name){
        // MySQL関数互換モードの場合、mysql_fetch_field() の返り値にないプロパティは飛ばす
        if($compat == true && ! in_array($name, $compat_flags)) continue;
        $obj->$name = ($value & $num) ? 1 : 0;
      }
      break;
    case "type":
      $obj->type = $types[$value];
      // $obj->numericはflagsの中には含まれていないため、typeを見て判断する
      $obj->numeric = (in_array($value, $numeric_types)) ? 1 : 0;
      // TEXT型の場合、typeはblobとなるがflagsではblobとして扱われないためここでフラグを立てる
      if($obj->type == "blob") $obj->blob = 1;
      break;
    default:
      // MySQL関数互換モードの場合、mysql_fetch_field() の返り値にないプロパティは飛ばす
      if($compat == true && in_array($key, $nocompat_keys)) continue;
      $obj->$key = $value;
      break;
    }
  }
  // オブジェクトを配列にキャストして処理する場合、キャスト後の配列の順序はプロパティ定義順となる。
  // そのため、MySQL関数互換モードの場合MySQL関数と同一の定義順に並び替える。
  if($compat == true){
    $sorted = new stdClass();
    foreach($compat_sortkey as $key){
      $sorted->$key = $obj->$key;
    }
    $obj = $sorted;
  }
  return $obj;
}
```

### mysql_field系関数

`mysql_field`系関数の単一情報を取得する関数は廃止となり、 `mysqli_fetch_field_direct()` 等で一括で取得する必要がある。
`mysql_fetch_field()` と同様にフラグ情報や型情報が整数型となるため、変換する必要がある。

上記 `mysql_fetch_field()` にて実装した関数に加え、下記の実装を利用することで `mysql_field`系関数と完全互換の値を取得することが可能となる。

```php
/**
 * mysqli_fetch_field_direct()の結果からmysql_field_*()の各種返り値と同一形式のデータを作成し返す
 * XXX: 順次mysqliの正式な関数に対応するときのため、フラグやタイプをMySQLi準拠のものに切り替えられるよう互換フラグを渡せるようにしている。
 *
 * @param mysqli_result MySQLi結果オブジェクト
 * @param int フィールド番号
 * @param string 対象となるmysqli_fetch_field_direct()関数の返り値オブジェクトのプロパティ名
 * @param bool MySQL関数互換フラグ
 * @return mixed
 */
function mysqli_field_compat(&$res, $offset, $key, $compat = true){
  $field = mysqli_fetch_field_direct($res, $offset);
  $result = false;
  if($field){
    switch($key){
    case "flags":
      $list = array();
      $flags = get_flag_names($compat);
      foreach($flags as $num => $name){
        if($field->flags & $num) $list[] = $name;
      }
      $result = implode(" ", $list);
      break;
    case "type":
      $types = get_type_names($compat);
      if(!is_null($types[$field->type])) $result = $types[$field->type];
      break;
    default:
      $result = $field->$key;
      break;
    }
  }
  return $result;
}

/**
 * mysql_field_flags()互換関数
 * XXX: mysqliの正式な関数に対応するときのため、フラグやタイプをMySQLi準拠のものに切り替えられるよう互換フラグを渡せるようにしている。
 *
 * @param mysqli_result MySQLi結果オブジェクト
 * @param int フィールド番号
 * @return string
 */
function mysqli_field_flags(&$res, $offset, $compat = true){
  return mysqli_field_compat($res, $offset, "flags", $compat);
}

/**
 * mysql_field_type()互換関数
 * XXX: mysqliの正式な関数に対応するときのため、フラグやタイプをMySQLi準拠のものに切り替えられるよう互換フラグを渡せるようにしている。
 *
 * @param mysqli_result MySQLi結果オブジェクト
 * @param int フィールド番号
 * @return string
 */
function mysqli_field_type(&$res, $offset, $compat = true){
  return mysqli_field_compat($res, $offset, "type", $compat);
}

/**
 * mysql_field_len()互換関数
 *
 * @param mysqli_result MySQLi結果オブジェクト
 * @param int フィールド番号
 * @return int
 */
function mysqli_field_len(&$res, $offset){
  return mysqli_field_compat($res, $offset, "length");
}

/**
 * mysql_field_name()互換関数
 *
 * @param mysqli_result MySQLi結果オブジェクト
 * @param int フィールド番号
 * @return string
 */
function mysqli_field_name(&$res, $offset){
  return mysqli_field_compat($res, $offset, "name");
}

/**
 * mysql_field_table()互換関数
 *
 * @param mysqli_result MySQLi結果オブジェクト
 * @param int フィールド番号
 * @return object
 */
function mysqli_field_table(&$res, $offset){
  return mysqli_field_compat($res, $offset, "table");
}
```

### mysql_result()

```php
/**
 * mysql_result()互換関数
 * XXX: mysqli_resultクラスと区別するため、関数名に接尾語として「_compat」を付けている。
 *
 * @param mysqli_result MySQLi結果オブジェクト
 * @param int 行番号
 * @param int フィールド番号
 * @return string
 */
function mysqli_result_compat(&$res, $row, $field = 0){
  mysqli_data_seek($res, $row);
  $fields = (is_int($field)) ? mysqli_fetch_row($res) : mysqli_fetch_assoc($res);
  return (is_null($fields[$field])) ? false : $fields[$field];
}
```
    
### mysql_unbuffered_query()

```php
/**
 * mysqli_unbuffered_query()互換関数
 * XXX: 他のMySQLi関数と統一性を持たせるため、mysql_unbuffered_query()とは引数の順序が異なる
 *
 * @param mysqli MySQLi接続オブジェクト
 * @param string SQL
 * @return string
 */
function mysqli_unbuffered_query(&$conn, $query){
  mysqli_real_query($conn, $query);
  return mysqli_use_result($conn);
}
```

追記
----

[[PHP] mysqli_stmt が使いにくかったのでラッパー関数を作った](http://qiita.com/from_kyushu/items/e367962cc51a87bb6f0d) というこの記事の続きを書いた。
MySQLi関数でプリペアドステートメントの利用を考えている方は併せて参考にしていただければ幸いである。