汎用PDOエミュレータ
2008年11月17日
主に(な)さんへ(まだ、SkinabbleAdminでPDO使ってたらだけど)。
JeansでのPDOエミュレータを、バージョンアップした。SQLiteだけでなく、MySQLやpgSQLでの使用も視野に入れてある。
現在のJeans(v.2.1.0.4)では、次のように記述してある。まず、PDOクラスとPDOStatementクラスのエミュレーションから。それぞれ、sql_pdo及びsql_pdostatementクラスとして記述。
見てのとおり、SQLiteに特異的な関数のコールは排除してある。代わりに、sql_biosクラスを継承しすることで、そこで記述したsql_xxxメソッドを呼び出している。SQLiteの場合のsql_biosクラスは、次のように記述。
使用上の注意点としては、文字列のクオートには必ずシングルクオート( ' )を利用すること。今回のバージョンでは、errorCode()とerrorInfo()に対応した。
JeansでのPDOエミュレータを、バージョンアップした。SQLiteだけでなく、MySQLやpgSQLでの使用も視野に入れてある。
現在のJeans(v.2.1.0.4)では、次のように記述してある。まず、PDOクラスとPDOStatementクラスのエミュレーションから。それぞれ、sql_pdo及びsql_pdostatementクラスとして記述。
/*
* General PDO emulator
* Note that sql_bios must be defined for each SQL engines in sqlite_ord.php etc.
* Note that sql_pdo class must be defined in sqlite.php, mysql.php etc
* if real PDO is used.
*/
class sql_pdo extends sql_bios {
public function __construct($dsn,$user,$passwd){
return $this->sql_open($dsn,$user,$passwd);
}
public function quote($data){
return self::sql_quote($data);
}
public function __destruct(){
core::shutdown($this);
}
public function beginTransaction(){
return (bool)$this->sql_query('BEGIN;');
}
public function commit(){
return (bool)$this->sql_query('COMMIT;');
}
/* Following methods are not impremented.
* getAttribute,lastInsertId,rollBack,setAttribute
*/
public function exec($query){
return $this->sql_exec($query);
}
public function query($query, $fetchMode=false){
$res= new sql_pdostatement($this, $this->db,$query);
if ($fetchMode!==false) $res->setFetchMode($fetchMode);
$res->execute();
return $res;
}
public function prepare($query){
return new sql_pdostatement($this, $this->db,$query);
}
public function errorCode($e=null){
if ($e===null) $e=$this->sqlerror;
switch($e){
case 0:
return '00000'; //Success
default:
return 'HY000'; //General error
}
}
public function errorInfo($e=null){
if ($e===null) $e=$this->sqlerror;
return array($this->errorCode($e), $e, sql_error_string($e));
}
}
class sql_pdostatement extends sql_bios {
/*
* Following methods are not implemented.
* setAttributes, getAttribute, getColumnMeta, nextRowset
*/
private $pdo, $query, $params=array(), $res=false;
public function __construct(&$pdo, $db, $query){
// Receive the database connection resource and prepared statement.
// Note that "$query" is a prepared statement or a completed query.
$this->pdo=&$pdo;
$this->db=$db;
$this->query=$query;
}
public function errorCode(){
return $this->pdo->errorCode($this->sqlerror);
}
public function errorInfo(){
return $this->pdo->errorInfo($this->sqlerror);
}
public function execute($params=false){
if ($params) {
foreach($params as $key=>$value){
if (is_int($key)) $this->params[(string)($key+1)]=$value;
else $this->params[$key]=$value;
}
}
// Construct the query from prepared statement and parameters.
if (0<$this->execute_cb()) {// Initialize the static values
$query=preg_replace_callback("/([^'\?]*|\?|'[^']*')/",array($this,'execute_cb'),$this->query);
} else $query=$this->query;
return (bool)($this->res=$this->sql_query($query));
}
private function execute_cb($matches=false){
static $search,$replace,$repbynum,$num;
if (!$matches) {
krsort($this->params); // To avoid maching longer name by shorter name, for example, :abc vs :abcde
// Initialize static values.
$search=$replace=$repbynum=array();
foreach($this->params as $key=>$value) {
$search[]=$key;
$replace[]=$repbynum[$key]="'".self::sql_quote($value)."'";
}
$num=0;
return count($search);
}
switch($matches[0]{0}){
case "'": // Inside the string.
return $matches[0];
case '?': // '?' used.
$num++;
return $repbynum[(string)$num];
default: // ':xxx' may be used.
return str_replace($search,$replace,$matches[0]);
}
}
public function bindParam($key, &$var){
$this->params[$key]=&$var;
}
public function bindValue($key, $value){
$this->params[$key]=$value;
}
private $fetchMode=PDO::FETCH_BOTH;// Either both, num, assoc, or obj
public function setFetchMode($fetchMode){
switch($fetchMode){
case PDO::FETCH_BOTH:
case PDO::FETCH_NUM:
case PDO::FETCH_ASSOC:
case PDO::FETCH_OBJ:
break;
default:
return false;
}
$this->fetchMode=$fetchMode;
return 1;
}
private $bindColumnData=array();
public function bindColumn($column, &$param){
$this->bindColumnData=&$param;
return true;
}
public function closeCursor(){
return true;
}
/*
* fetch methods follow
*/
public function fetch($fetch_style=false){
if ($fetch_style===false) $fetch_style=$this->fetchMode;
$ret=$this->sql_fetch($this->res,$fetch_style);
foreach($this->bindColumnData as $key=>&$value){
if (is_object($ret)) $value=$ret->$key;
else $value=$ret[$key];
}
return $ret;
}
public function fetchObject(){
return $this->fetch(PDO::FETCH_OBJ);
}
public function fetchAll($fetch_style=false){
$ret=array();
while($row=$this->fetch($fetch_style)) $ret[]=$row;
return $ret;
}
public function fetchColumn($column_number=0){
if ($row=$this->sql_fetch($this->res,PDO::FETCH_BOTH)) return $row[$column_number];
else return false;
}
public function columnCount(){
return self::sql_columnCount($this->res);
}
public function rowCount(){
return self::sql_rowCount($this->db);
}
}見てのとおり、SQLiteに特異的な関数のコールは排除してある。代わりに、sql_biosクラスを継承しすることで、そこで記述したsql_xxxメソッドを呼び出している。SQLiteの場合のsql_biosクラスは、次のように記述。
/*
* Abstract class for PDO emulator
* See also sql_pdo and sql_pdostatements classes in sql_pdo.php
*/
abstract class sql_bios {
protected $db=false, $sqlerror=0;
protected function sql_open($dsn,$user,$passwd){
if ($this->db) return;
$dsn=preg_replace('/^.*:/','',$dsn);
if (!($this->db=sqlite_open($dsn))) core::exitWithError('SQLite connection error.');
}
protected function sql_quote($data){
return sqlite_escape_string($data);
}
protected function sql_query($query){
$ret=sqlite_query($this->db,$query);
$this->sqlerror=sqlite_last_error($this->db);
return $ret;
}
protected function sql_exec($query){
$ret=sqlite_exec($this->db,$query);
$this->sqlerror=sqlite_last_error($this->db);
return $ret;
}
protected function sql_error_string($e_code){
return sqlite_error_string($e_code);
}
protected function sql_columnCount($res){
return sqlite_num_fields($res);
}
protected function sql_rowCount($db){
return sqlite_changes($db);
}
protected function sql_fetch($res,$result_type=null){
if ($result_type===null) $result_type=$this->fetchMode;
switch($result_type){
case PDO::FETCH_BOTH:
return sqlite_fetch_array($res,SQLITE_BOTH);
case PDO::FETCH_NUM:
return sqlite_fetch_array($res,SQLITE_NUM);
case PDO::FETCH_ASSOC:
return sqlite_fetch_array($res,SQLITE_ASSOC);
case PDO::FETCH_OBJ:
return sqlite_fetch_object($this->res);
default:
return false;
}
}
}使用上の注意点としては、文字列のクオートには必ずシングルクオート( ' )を利用すること。今回のバージョンでは、errorCode()とerrorInfo()に対応した。