汎用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()に対応した。