让ThinkPHP3.2.3 支持对Oracle LOB数据的操作
1. 问题由来
直接用PHP存取oracle的clob也是会自动截断。当需要插入的数据很大,会直接报错。ThinkPHP 3.2.3 封装的CURD也不能解决这个问题。
这个时候就需要使用PHP OCI8的拓展函数库。
2. 配置PHP:OCI8
首先,原理是利用PHP OCI8的拓展函数库。所以,必须先把OCI8,和php_oci8和php_oci8_11 功能开启。
可以wampserver上直接进行设置。如下图:
设置成功后,可以通过phpinfo() 查看,显示enabled即开启成功,如下图。
2. PHP:OCI8的CURD
PHP对Clob文件是以resource的类型存取的, 所以PDO对Clob资源文件的读取应该这样操作:
$arr = $pdo -> fetch();
is_resource($arr[‘clob’]) && $arr[‘clob’]=stream_get_contents($arr[‘clob’]);
可以对照参考:PHP:OCI8 手册
更多CURD的Examples可以参考 官方范例
$c1 = oci_connect("hr", "welcome", 'localhost/XE');
$c2 = oci_connect("hr", "welcome", 'localhost/XE');
// Both $c1 and $c2 show the same PHP resource id meaning they use the
// same underlying database connection
echo "c1 is $c1<br>\n";
echo "c2 is $c2<br>\n";
function create_table($conn)
{
$stmt = oci_parse($conn, "create table hallo (test varchar2(64))");
oci_execute($stmt);
echo "Created table<br>\n";
}
function drop_table($conn)
{
$stmt = oci_parse($conn, "drop table hallo");
oci_execute($stmt);
echo "Dropped table<br>\n";
}
function insert_data($connname, $conn)
{
$stmt = oci_parse($conn, "insert into hallo
values(to_char(sysdate,'DD-MON-YY HH24:MI:SS'))");
oci_execute($stmt, OCI_DEFAULT);
echo "$connname inserted row without committing<br>\n";
}
function rollback($connname, $conn)
{
oci_rollback($conn);
echo "$connname rollback<br>\n";
}
function select_data($connname, $conn)
{
$stmt = oci_parse($conn, "select * from hallo");
oci_execute($stmt, OCI_DEFAULT);
echo "$connname ----selecting<br>\n";
while (oci_fetch($stmt)) {
echo " " . oci_result($stmt, "TEST") . "<br>\n";
}
echo "$connname ----done<br>\n";
}
create_table($c1);
insert_data('c1', $c1); // Insert a row using c1
sleep(2); // sleep to show a different timestamp for the 2nd row
insert_data('c2', $c2); // Insert a row using c2
select_data('c1', $c1); // Results of both inserts are returned
select_data('c2', $c2); // Results of both inserts are returned
rollback('c1', $c1); // Rollback using c1
select_data('c1', $c1); // Both inserts have been rolled back
select_data('c2', $c2);
drop_table($c1);
// Closing one of the connections makes the PHP variable unusable, but
// the other could be used
oci_close($c1);
echo "c1 is $c1<br>\n";
echo "c2 is $c2<br>\n";
// Output is:
// c1 is Resource id #5
// c2 is Resource id #5
// Created table
// c1 inserted row without committing
// c2 inserted row without committing
// c1 ----selecting
// 09-DEC-09 12:14:43
// 09-DEC-09 12:14:45
// c1 ----done
// c2 ----selecting
// 09-DEC-09 12:14:43
// 09-DEC-09 12:14:45
// c2 ----done
// c1 rollback
// c1 ----selecting
// c1 ----done
// c2 ----selecting
// c2 ----done
// Dropped table
// c1 is
// c2 is Resource id #5
?>
注意:
1.[oci_connect()]连接数据库时,为了防止乱码,建立连接的时候就指定字符编码。
// OCILogon 是oci_connect的别名, 不建议使用OCILogon
oci_connect($username, $password, $dbsid, $charset);
OCILogon($username, $password, $dbsid, $charset);
2.oci_execute() 执行一条之前被解析过的语句(见 oci_parse())。可选参数 mode 允许定义执行模式(默认是 OCI_COMMIT_ON_SUCCESS)。如果不需要将语句自动提交,则需要把 mode 设为 OCI_DEFAULT。
用 OCI_DEFAULT 模式时,将建立一个事务。事务会在关闭连接或脚本结束时(看哪个先)自动回卷。需要明确调用 oci_commit() 来提交事务,或者 oci_rollback() 中止事务。
成功时返回 TRUE, 或者在失败时返回 FALSE。
3. ThinkPHP 的Oracle Clob操作类:
[引用自ThinkPHP社区《分享自己的oracel类,支持多CLOB操作及主键自增加》]
class Ociclob{
var $conn;
var $table;
var $seqname;
var $where;//查询条件,字符串
var $lob;//lob字段名,数组
var $data;//数据数组
//架构函数
public function Ociclob($table='',$data=''){
//取TP框架的数据库配置
$this->connect(C('DB_USER'),C('DB_PWD'),C('DB_NAME'));
if(!empty($table)) $this->table=$table;
if(!empty($table)&&!empty($data)){
$this->checkfield($table,$data);
}
}
//检测字段属性
public function checkfield($table,$data){
if(!empty($table)&&!empty($data)){
$fields=$this->getFields($table);
foreach ($data as $key=>$value){
//检测lob字段
if(strtolower($fields[strtolower($key)]['type'])=='clob') $lob[]=$key;
//检测PK字段并获取SEQ
if(strtolower($fields[strtolower($key)]['primary'])==1){
$this->seqname=$value;
$this->data[$key]=$this->getseq();//根据自动填充主键值
$pk=$key;//主键被设置标志
}
}
$this->lob=$lob;
//如果没有在DATA中的设置主键值,则根据SEQNAME自动填充
if(!isset($pk)&&!empty($this->seqname)) {
$this->data[$fields['pk']]=$this->getseq();
}
unset($lob);
unset($pk);
}
}
/**
+----------------------------------------------------------
* 连接ORACLE
+----------------------------------------------------------
*/
public function connect($user, $password, $SID){
// 创建连接,并设置字符编码为'UTF8'.
$this->conn = OCILogon($user, $password, $SID, 'UTF8');
}
/**
+----------------------------------------------------------
* 设置ORACLE字符集
+----------------------------------------------------------
*/
public function charset($code='UTF8'){
$sql="ALTER DATABASE CHARACTER SET $code";
$stmt = oci_parse($this->conn, $sql);
oci_execute($stmt);
oci_commit($this->conn);
// Free resources
oci_free_statement($stmt);
}
/**
+----------------------------------------------------------
* 添加包含有CLOB字段的记录
+----------------------------------------------------------
*/
public function insert (){
//检测字段属性
if(empty($this->lob)) $this->checkfield($this->table,$this->data);
//字段整理
$f=strtoupper(join(',',array_keys($this->data)));
//数据整理
foreach ($this->data as $key=>$val){
$f_v_arr[]=!in_array($key,$this->lob)?"'".$val."'":"EMPTY_CLOB()";
}
$f_v=join(',',$f_v_arr);
//lob字段清理并赋值LOB数据到绑定变量
for ($i=0;$i<count($this->lob);$i++){
$lob_str.=":".$this->lob[$i]."_loc,";
}
$returning_str.=" RETURNING ".join(',',$this->lob)." INTO ".rtrim($lob_str,',');
//组装SQL
$sql = "INSERT INTO $this->table ($f) VALUES (".$f_v.")".$returning_str ;
$stmt = oci_parse($this->conn, $sql);
for ($i=0;$i<count($this->lob);$i++){
// 创建一个“空”的OCI LOB对象绑定到定位器
$$this->lob[$i] = oci_new_descriptor($this->conn, OCI_D_LOB);
$lob_str=":".$this->lob[$i]."_loc";
// 将Oracle LOB定位器绑定到PHP LOB对象
oci_bind_by_name($stmt, $lob_str, $$this->lob[$i], -1, OCI_B_CLOB);
}
// 执行该语句的使用,oci_default -作为一个事务
oci_execute($stmt, OCI_DEFAULT) or die ("Unable to execute query\n");
// 保存LOB对象数据
for ($i=0;$i<count($this->lob);$i++){
if(!$$this->lob[$i]->save($this->data[$this->lob[$i]])){
$result=false;
break;
}
}
if ( isset($result)&&$result==false ) {
// 如果错误,则回滚事务
oci_rollback($this->conn);
$ret=false;
} else {
// 如果成功,则提交
oci_commit($this->conn);
$ret=true;
}
// 释放资源
oci_free_statement($stmt);
for ($i=0;$i<count($this->lob);$i++){
$$this->lob[$i]->free();
}
return $ret;
}
/**
+----------------------------------------------------------
* 更新CLOB字段的内容
+----------------------------------------------------------
*/
public function update (){
//检测字段属性
if(empty($this->lob)) $this->checkfield($this->table,$this->data);
//数据整理
foreach ($this->data as $key=>$val){
$set_arr[]=!in_array($key,$this->lob)?strtoupper($key)."='".$val."'":$key."=EMPTY_CLOB()";
}
$set_str=join(',',$set_arr);
//lob字段清理并赋值LOB数据到绑定变量
for ($i=0;$i<count($this->lob);$i++){
$lob_str.=":".$this->lob[$i]."_loc,";
}
$returning_str.=" RETURNING ".join(',',$this->lob)." INTO ".rtrim($lob_str,',');
$where_str=strtoupper($this->where);
//组装SQL
$sql = "UPDATE $this->table SET $set_str WHERE $where_str ".$returning_str;
$stmt = OCIParse($this->conn, $sql);
for ($i=0;$i<count($this->lob);$i++){
// 创建一个“空”的OCI LOB对象绑定到定位器
$$this->lob[$i] = OCINewDescriptor($this->conn, OCI_D_LOB);
$lob_str=":".$this->lob[$i]."_loc";
// 将Oracle LOB定位器绑定到PHP LOB对象
OCIBindByName($stmt, $lob_str, $$this->lob[$i], -1, OCI_B_CLOB);
}
// 执行该语句的使用,oci_default -作为一个事务
OCIExecute($stmt, OCI_DEFAULT) or die ("Unable to execute query\n");
// 保存LOB对象数据
for ($i=0;$i<count($this->lob);$i++){
if(!$$this->lob[$i]->save($this->data[$this->lob[$i]])){
$result=false;
break;
}
}
if ( isset($result)&&$result==false ) {
O*llback($this->conn);
$ret=false;
}else $ret=true;
// 提交事务
OCICommit($this->conn);
//释放资源
for ($i=0;$i<count($this->lob);$i++){
$$this->lob[$i]->free();
}
OCIFreeStatement($stmt);
return $ret;
}
public function getseq(){
$sql="select $this->seqname.currval from dual";
$stmt = oci_parse($this->conn, strtoupper($sql));
oci_execute($stmt);
$data = array();
while ( $row = oci_fetch_array($stmt, OCI_ASSOC+OCI_RETURN_LOBS) ) {
if($row == null) continue;
array_push($data, $row);
}
// 释放资源
oci_free_statement($stmt);
array_filter($data);
return $data[0]['CURRVAL'];
}
/**
+----------------------------------------------------------
* 查询包含有CLOB字段的记录
+----------------------------------------------------------
*/
public function select ($sql=''){
$sql = empty($sql) ? "SELECT * FROM $this->table WHERE $this->where ":$sql;
$stmt = oci_parse($this->conn, strtoupper($sql));
oci_execute($stmt);
while ( $row = oci_fetch_array($stmt, OCI_ASSOC+OCI_RETURN_LOBS) ) {
$data[]=$row;
}
// 释放资源
oci_free_statement($stmt);
return $data;
}
/**
* 取得数据表的字段信息
* @access public
*/
public function getFields($tableName) {
$sql="select a.column_name,data_type,decode(nullable,'Y',0,1) notnull,data_default,decode(a.column_name,b.column_name,1,0) pk "
."from user_tab_columns a,(select column_name from user_constraints c,user_cons_columns col "
."where c.constraint_name=col.constraint_name and c.constraint_type='P'and c.table_name='".strtoupper($tableName)
."') b where table_name='".strtoupper($tableName)."' and a.column_name=b.column_name(+)";
$result= $this->select ($sql);
$info = array();
if($result) {
foreach ($result as $key => $val) {
$info[strtolower($val['COLUMN_NAME'])] = array(
'name' => strtolower($val['COLUMN_NAME']),
'type' => strtolower($val['DATA_TYPE']),
'notnull' => $val['NOTNULL'],
'default' => $val['DATA_DEFAULT'],
'primary' => $val['PK'],
'autoinc' => $val['PK'],
);
if($val['PK']==1) $info['pk']=$val['COLUMN_NAME'];
}
}
return $info;
}
}
?>
Ociclob 的调用样例:
假设已经存在一个表名为’tb_post’的表(表的主键为’postid’,并且是自增的),以及一个名为’SEQ_POST’的序列名
/**
* $data 要插入到表的数据
* 返回值 插入数据的ID
*/
function insertPostWithClob($data) {
$clog_obj=new Ociclob();
$clog_obj->table='tb_post';
$clog_obj->seqname='SEQ_POST';
$clog_obj->data = $data;
if($clog_obj->insert() == false) return -1;
$ret = $clog_obj->getseq();
return $ret;
}
/**
* $data 要插入到表的数据
* 返回值 是否修改成功
*/
function updatePostWithClob($data) {
$postid = $data['postid'];
$clog_obj=new Ociclob();
$clog_obj->table='tb_post';
$clog_obj->where="postid='$postid'";
$clog_obj->data = $data;
return $clog_obj->update();
}
/**
* $postid
* 返回值 查询得到的list
*/
function selectPostWithClob($postid) {
$clog_obj=new Ociclob();
$clog_obj->table='tb_post';
$clog_obj->where="postid='$postid'";
$ret=$clog_obj->select();
return $ret;
}