次に、PHPプログラムからSQLite等のデータベースを操作する場合の共通ライブラリ PDO について紹介します
SQLiteは、PDOを使わなくても操作することが出来ますが、PDO ライブラリを使って記述しておけば、基本的には、MySQL や SQLite 等のデータベースの種類に関係なく同じ記述でデータベースを扱うことが出来るので、データベース操作の記述が統一され記述しやすくなり、データベースを移 行する場合等でも最小限の変更で済むようになります
※PDOを使っても、各データベースごとにサポートしているSQL文の違いがあるので全てを共通化出来るわけではありません
PDO
今回作成したプログラムで主に使っているPDOのメソッド(関数)について紹介します
よく使うのは、データベース接続とプリペアードステートメントを使ったSQL実行、結果セットの取得、トランザクションです。
データベース接続 | __construct | SQLite データベースファイルを絶対パスで指定します
データベースファイルがない場合は、空のデータベースファイル作成を行い、存在する場合はデータベースファイルとの接続を確立します |
SQL実行 (SQLエスケープなし) |
exec | 1回のみでSQLエスケープ不要な信頼できるSQLを実行します
※SELECT文以外の結果取得が不要な場合は、exec を使用し、SELECT文で結果セットを PDOStatement オブジェクトとして取得する場合は query を使用します |
SQL実行 (プリペアードステートメントによるSQLエスケープ) |
prepare
|
SQL文にプレースホルダ(変数)を使い信頼出来ない値がセットされる可能性がある場合は、SQLインジェクション対策のために prepared, bindValue, execute を使ってSQLエスケープを行い、安全なSQL文にして実行します
※プレースホルダとPHPでの変数値をバインドさせるには bindValue(変数の値をバインド)、bindParam(変数の参照アドレスとしてバインド)があります |
SELECT結果セット取得 | fetch | SELECTの実行結果(PDOStatement オブジェクト)からデータを取得します
取得データが単一か複数データかによって使用関数が異なります。また、配列として取得するか、オブジェクトとして取得するかによっても異なります |
トランザクション
|
biginTransaction | リレーショナル・データベースは、多くの場合複数のテーブルからなり、データを追加、更新、削除する場合には、そのテーブル間等でデータの整合性が保たれ ている必要があります。その為にトランザクションというデータ追加、更新、削除に対して関連するデータに対する処理が全て正常に行われた場合に反映させ、何か問題があった場合 は元に戻す機能があります
SQLiteの場合はサーバーレスのファイル型データベースですので、データ追加、更新時 はトランザクションを使ってある程度まとまったデータを処理したほうがディスクI/O操作を減らせるので高速に実行することが出来ます。但し、トランザク ション時間があまり長いとその間は他のクエリーが待たされてロックエラーとなることも考えられますので、一度に処理する最大データ量はバランスよく見極め る必要があります |
データベースクローズ | デストラクト | PDOのインスタンスに NULL をセットするとクローズされます 明示的にクローズしなくともインスタンスが破棄されるときに自動的にクローズされます |
参照 http://php.net/manual/ja/book.pdo.php
詳細はリンク先サイトの説明を参照して下さい
Ext_PDO
プログラムを作成するにあたり、PDOを直接扱うコードを書くのはちょっと煩わしかったので、少し扱いやすくなるようにラップした Ext_PDO という拡張クラスを書いてみました
WordPress で MySQL を扱う wpdb クラスにちょっと似た感じで使えるように作りました
SQLの実行と結果データの取得をデータ取得無し、変数(カラム)、行(レコード)、複数行取得に分け、各々プリペアードステートメントの有無で分けています。
データ取得のないSQL実行 | sql_exec($sql) sql_prepare_exec($sql, $ar_param) |
変数(カラム)データ取得 | sql_get_var($sql, $col_num=0) sql_prepare_var($sql, $ar_param, $col_num=0) |
行(レコード)データ取得 | sql_get_row($sql, $output=PDO::FETCH_OBJ) sql_prepare_row($sql, $ar_param, $output= PDO::FETCH_OBJ) |
複数の行データ取得 | sql_get_results($sql, $output = PDO::FETCH_OBJ) sql_prepare_results($sql, $ar_param, $output= PDO::FETCH_OBJ) |
各メソッドの詳細は、短いコードですので直接見て下さい m(_ _)m
<?php /** * PDO(SQlite)の拡張クラス * * * @author enomoto celtislab */ class Ext_PDO extends PDO { public function __construct( $dsn, $DB_USER='', $DB_PASS='' ) { parent::__construct( $dsn, $DB_USER, $DB_PASS); $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } //デバッグ時のエラー表示処理 static function display_error(PDOException $e) { //if (ini_get( 'display_errors' )){ if ( defined('WP_DEBUG') && WP_DEBUG ){ var_dump($e->getMessage()); } } /** * データ取得のないSQL文の実行 * @param string $sql SQL文 * @return */ public function sql_exec($sql) { try { $res = $this->exec( $sql ); return $res; } catch (PDOException $e) { if(PDO::inTransaction()){ throw $e; } else { self::display_error($e); return false; } } } /** * 変数(カラム)データ取得 * ※bool 値のカラムに対しては使用不可(falseの区別が出来ない) * @param string $sql SQL文 * @return */ public function sql_get_var($sql, $col_num=0) { try { $stmt = $this->query( $sql ); $var = $stmt->fetchColumn( $col_num ); return $var; } catch (PDOException $e) { if(PDO::inTransaction()){ throw $e; } else { self::display_error($e); return false; } } } /** * 行(レコード)データを取得 * @param string $sql SQL文 * @param $output * PDO::FETCH_ASSOC - 結果を連想配列として出力。 * PDO::FETCH_NUM - 結果をインデックス配列として出力。 * 上記以外の指定 - 結果をオブジェクトとして出力。 * @return */ public function sql_get_row($sql, $output=PDO::FETCH_OBJ) { try { $stmt = $this->query( $sql ); if($output === PDO::FETCH_ASSOC || $output === PDO::FETCH_NUM ){ $row = $stmt->fetch( $output ); } else { $row = $stmt->fetchObject(); } return $row; } catch (PDOException $e) { if(PDO::inTransaction()){ throw $e; } else { self::display_error($e); return false; } } } /** * 複数の行データを取得 * @param string $sql SQL文 * @param $output * PDO::FETCH_ASSOC - 結果を連想配列として出力。 * PDO::FETCH_NUM - 結果をインデックス配列として出力。 * 上記以外の指定 - 結果をオブジェクト配列として出力。 * @return */ public function sql_get_results($sql, $output = PDO::FETCH_OBJ) { try { $stmt = $this->query( $sql ); if($output === PDO::FETCH_ASSOC || $output === PDO::FETCH_NUM ){ $results = $stmt->fetchAll( $output ); } else { $stmt->setFetchMode(PDO::FETCH_CLASS, 'stdClass'); $results = $stmt->fetchAll(); } return $results; } catch (PDOException $e) { if(PDO::inTransaction()){ throw $e; } else { self::display_error($e); return false; } } } /** * プリペアドステートメント(セキュリティ対策) * @param string $sql 例. "INSERT INTO table (id, title, author, date) VALUES ( ?, ?, ?, ?)" * @param array $ar_param 例. array($id, $title, $author, $now->format('Y-m-d H:i:s') * @param $output * @return */ // データ取得のないSQL文の実行 public function sql_prepare_exec($sql, $ar_param) { try { $stmt = $this->prepare( $sql ); $stmt = self::_stmt_bind( $stmt, $ar_param ); return self::_stmt_execute( $stmt ); } catch (PDOException $e) { if(PDO::inTransaction()){ throw $e; } else { self::display_error($e); return false; } } } // 変数(カラム)データ取得 public function sql_prepare_var($sql, $ar_param, $col_num=0) { try { $stmt = $this->prepare( $sql ); $stmt = self::_stmt_bind( $stmt, $ar_param ); $var = $stmt->execute(); if($var !== false) $var = $stmt->fetchColumn( $col_num ); return $var; } catch (PDOException $e) { if(PDO::inTransaction()){ throw $e; } else { self::display_error($e); return false; } } } // 行(レコード)データを取得 public function sql_prepare_row($sql, $ar_param, $output= PDO::FETCH_OBJ) { try { $stmt = $this->prepare( $sql ); $stmt = self::_stmt_bind( $stmt, $ar_param ); $row = self::_stmt_execute( $stmt, $output, true); return $row; } catch (PDOException $e) { if(PDO::inTransaction()){ throw $e; } else { self::display_error($e); return false; } } } // 複数の行データを取得 public function sql_prepare_results($sql, $ar_param, $output= PDO::FETCH_OBJ) { try { $stmt = $this->prepare( $sql ); $stmt = self::_stmt_bind( $stmt, $ar_param ); return self::_stmt_execute( $stmt, $output); } catch (PDOException $e) { if(PDO::inTransaction()){ throw $e; } else { self::display_error($e); return false; } } } //SQL prepare バインド static function _stmt_bind($stmt, $ar_param) { $n = 1; foreach ($ar_param as $val){ if(is_string($val)){ $stmt->bindValue($n, $val, PDO::PARAM_STR); } else if(is_null($val)){ $stmt->bindValue($n, NULL, PDO::PARAM_NULL); } else if(is_bool($val)){ $stmt->bindValue($n, $val, PDO::PARAM_BOOL); } else if(is_int($val)){ $stmt->bindValue($n, (int)$val, PDO::PARAM_INT); } else if(is_float($val)){ //PDOでは実数型指定ができないがSQLiteはREAL型として扱うようキャストしておく $stmt->bindValue($n, (float)$val); } else { //ラージオブジェクト型(バイナリデータ) $stmt->bindValue($n, $val, PDO::PARAM_LOB); } $n++; } return $stmt; } //SQL prepare 実行 static function _stmt_execute($stmt, $output = null, $row = false) { $res = $stmt->execute(); if($res === false) return false; if($output === null || $res === false) return $res; else { if($output === PDO::FETCH_ASSOC || $output === PDO::FETCH_NUM ){ $results = ($row)? $stmt->fetch( $output ) : $stmt->fetchAll( $output ); } else { $stmt->setFetchMode(PDO::FETCH_CLASS, 'stdClass'); $results = ($row)? $stmt->fetch() : $stmt->fetchAll(); } return $results; } } /** * テーブルの存在確認 * * @return boolean */ public function is_table_exist($table) { //sqlite_master から作成済みのテーブル構造を取得 $res = $this->sql_get_row("SELECT * FROM sqlite_master WHERE type = 'table' AND name = '$table'"); if($res === false) return false; return true; } }
これだけでもかなりコードが見やすく簡潔に書けるので自分では気に入っています (^^)
興味ある方は、使いやすいように自由に改造して使って下さい
セキュリティ対策
データベースでは、SQLを実行する時に不正なSQLが実行されないように対策 する必要があります。特に Web環境からのアクセスに対しては、外部から入力されたデータを使ってSQL文を生成して実行する場合が多いので、必ず、SQLインジェクション対策を 行い不正なアクセスを防止しなければなりません
PDOでは、プリペアードメソッド prepare でプレースホルダを使い、バインドでデータ型を指定することで最低限のSQLインジェクション対策を行うことが出来ます
WordPress なら、さらに、nonce を併用してより強固な不正アクセス対策を行いましょうhttp://tokkono.cute.coocan.jp/blog/slow/index.php/wordpress/secured-ajax-with-wp-nonce/
以降のPDOのサンプルコードはこの拡張クラス Ext_PDO を使った記述となります
次のページに続きます (^^)