回顾一个多月php与Yii与Oracle的碰撞

2018-09-09  本文已影响0人  sockball

临时抽调隔壁组参与一个半独立项目(其实还是在同一个办公室), 主要负责后台内容的维护, 权限管理部分为JAVA的上司, 数据库为oracle.
  刚开始时当然会因为换到一个不熟悉的数据库而担心出现很多解决不了的问题, 还好框架解决了最基本的事情:同样的方法仍能完成普通的增删改查, 不然要框架做什么呢不是? 然而在开发中还是遇到了一些问题, 于是特地回顾记录一下.

oci扩展

首要的问题就是安装php的oci扩展和oracle数据库, 不过数据库在内网已经搭建好了直接连接即可, 所以这部分就没有接触到了.. 至于oci扩展由于时隔久远(1个月多了), 只记得一定要选择和oracle数据库版本和位数一致的。(这里是oralce 11gR2 64位). 最后的db配置:

'db' => [
    'class' => 'yii\db\Connection',
    'dsn' => 'oci:dbname=//[ip]:[port]/orcl;',
    'username' => '#username',
    'password' => '#password'
]

自增

oracle中是没有自增的...在开始没有被告知的情况下(上司说是忘了说), 开始自己寻找解决办法:

// BaseModel
public function behavoirs()
{
    return [
        ...
        'id' => [
            'class' => IdBehavior::class
        ]
    ];
}
// IdBehavior
public function events()
{
    return [
        BaseModel::EVENT_BEFORE_INSERT => 'setId'
    ];
}

public function setId($event)
{
    $model = $event->sender;
    $tableName = $model::tableName();
    $sql = "SELECT function('{$tablenName}') FROM dual"; // function代表一个自定义获取序列值的函数
    $model->id = Yii::$app->db->createCommand($sql)->queryScalar();
}

clob字段类型

$file = './example.png';
$content = file_get_contents($file);
$length = filesize($file);
$sql = 'UPDATE table_name' .
        'SET clob_column = :clob' . 
        'WHERE id = :id';
Yii::$app->db
        ->createCommand($sql)
        ->bindParam(':clob', $content, \PDO::PARAM_STR, $length)
        ->bindParams(':id', $id)
        ->execute();

此处使用file_get_contents获得的值模拟长字符串, 其中在bindParam中指定$length是最重要的一点(尽管超过4000字节, 也仍能正常存储).
  另外虽然是clob类型字段, 若在此处指定类型为\PDO::PARAM_LOB, 则又会得到这样的报错:
ORA-00932: inconsistent datatypes: expected CLOB got BLOB
根据报错可猜测\PDO::PARAM_LOB是针对于blob类型的, 而此处的clob类型还是作为字符串处理

blob字段类型

上司这边的要求是图片、视频、音频资源全部存储到数据库的blob字段(无法驳回这种做法, 无奈), blob是存储二进制数据, 此前并没有存储过, 然后经历1天多的折腾最后才在官方PDO的手册中找到解决方法(example 3), 关键点在于开启事务否则只会隐式提交0长度. 以下是各种写法的尝试以及报错 (以图片存储为例)

$file = './example.png';
$content = file_get_contents($file);
$sql = 'UPDATE table_name' .
        'SET blob_column = :blob' . 
        'WHERE id = :id';
Yii::$app->db
        ->createCommand($sql)
        ->bindParam(':blob', $content)
        ->bindParams(':id', $id)
        ->execute();

报错: ORA-01461: can bind a LONG value only for insert into a LONG column
指定字符串类型与长度后bindParam(':blob', $content, \PDO::PARAM_STR, filesize($file))报错同上

$file = './example.png';
$content = file_get_contents($file);
$sql = 'UPDATE table_name' .
        'SET blob_column = :blob' . 
        'WHERE id = :id';
Yii::$app->db
        ->createCommand($sql)
        ->bindParam(':blob', $content, \PDO::PARAM_LOB)
        ->bindParams(':id', $id)
        ->execute();

报错supplied argument is not a valid stream resource 提示非resource类型于是舍弃file_get_contents方法

$file = './example.png';
$content = fopen($file, 'r');
$sql = 'UPDATE table_name' .
        'SET blob_column = :blob' . 
        'WHERE id = :id';
Yii::$app->db
        ->createCommand($sql)
        ->bindParam(':blob', $content, \PDO::PARAM_LOB)
        ->bindParams(':id', $id)
        ->execute();
fclose($content);

正常运行但数据库保存为空

$file = './example.png';
$content = fopen($file, 'rb');
$sql = 'UPDATE table_name' .
        'SET blob_column = empty_blob()' . 
        'WHERE id = :id' . 
        'RETURNING blob_column INTO :blob';
Yii::$app->db
        ->createCommand($sql)
        ->bindParam(':blob', $content, \PDO::PARAM_LOB)
        ->bindParams(':id', $id)
        ->execute();
fclose($content);

fopen的rb模式似乎不怎么常用, 它使用二进制模式读取文件, 而此处正是需要使用这种模式
  使用oracle的empty_blob函数初始化blob字段, 而此处的RETURNING INTO 更像是能为这个初始化做一个占位符的指定, 否则使用函数的同时无法再指定占位符.
  似乎根据报错已经来到了最终的解决方案, 然而数据库保存仍未空.

$file = './example.png';
$content = fopen($file, 'rb');
$sql = 'UPDATE table_name' .
        'SET blob_column = empty_blob()' . 
        'WHERE id = :id' . 
        'RETURNING blob_column INTO :blob';
$transaction = Yii::$app->db->beginTransaction();
try {
    Yii::$app->db
            ->createCommand($sql)
            ->bindParam(':blob', $content, \PDO::PARAM_LOB)
            ->bindParams(':id', $id)
            ->execute();
    $transaction->commit();
} catch (Exception $e) {
    // todo
}
fclose($content);


  对于取出后的注意点和clob都相同. 只是stream_get_contents后不能直接使用(二进制流), 这里的做法是base64_encode处理后再使用imgaudiovideo标签展示出(文件格式、以及标签使用base64这里不做说明, 这边对于音频、视频的不同格式的处理为统一使用ffmpeg转换mp3、mp4再存储)

最后

由于此项目已暂停(上面没有谈好钱的问题, 但并没有取消), 暂时遇到的问题只有这一些, 之后如果再出现新的问题会及时记录.

上一篇 下一篇

猜你喜欢

热点阅读