查询语句 (DQL) 的构造功能开发完毕,咱们再给查询构造器增长一些对 DML (Data Manipulation Language) 语句的支持,如简单的 insert、update、delete 操做。php
咱们先回顾下 PDO 原生的 insert 操做怎么进行:html
// 预编译 $pdoSt = $pdo->prepare("INSERT INTO test_table ('username', 'age') VALUES (:username, :age);"); // 绑定参数 $pdoSt->bindValue(':username', 'Jack', PDO::PARAM_STR) $pdoSt->bindValue(':age', 18, PDO::PARAM_INT) // 执行 $pdoSt->execute(); // 获取执行数据 $count = $pdoSt->rowCount(); // 返回被影响的行数 $lastId = $pdo->lastInsertId(); // 获取最后插入行的 ID
数据插入mysql
和查询语句的执行过程并无太大差异,只是语法不一样。回想第二篇,咱们新建了 _buildQuery() 方法去构造最终的 SQL,对于 insert,咱们在基类新建 _buildInsert() 方法:sql
protected function _buildInsert() { $this->_prepare_sql = 'INSERT INTO '.$this->_table.$this->_insert_str; }
基类添加 _insert_str 属性:数据库
protected $_insert_str = '';
修改 _reset() 函数,将 _insert_str 属性的初始化过程添加进去:数组
protected function _reset() { $this->_table = ''; $this->_prepare_sql = ''; $this->_cols_str = ' * '; $this->_where_str = ''; $this->_orderby_str = ''; $this->_groupby_str = ''; $this->_having_str = ''; $this->_join_str = ''; $this->_limit_str = ''; $this->_insert_str = ''; // 重置 insert 语句 $this->_bind_params = []; }
基类中添加 insert() 方法:函数
public function insert(array $data) { // 构建字符串 $field_str = ''; $value_str = ''; foreach ($data as $key => $value) { $field_str .= ' '.self::_wrapRow($key).','; $plh = self::_getPlh(); // 生产占位符 $this->_bind_params[$plh] = $value; //保存绑定数据 $value_str .= ' '.$plh.','; } // 清除右侧多余的逗号 $field_str = rtrim($field_str, ','); $value_str = rtrim($value_str, ','); $this->_insert_str = ' ('.$field_str.') VALUES ('.$value_str.') '; // 构造 insert 语句 $this->_buildInsert(); // 执行 $this->_execute(); // 获取影响的行数 return $this->_pdoSt->rowCount(); }
对上述代码,咱们申明了 insert() 方法的参数是一个键值数组,用来传入要插入的字段、值映射。默认返回被影响的行数 (比较通用)。post
测试测试
试着插入一条数据吧:fetch
$insert_data = [ 'username' => 'jack', 'age' => 18, ]; $results = $driver->table('test_table')->insert($insert_data);
获取最后插入行的 ID
当一个表中有自增 id 且为主键时,这个 id 能够被看做区分数据的惟一标识。而在插入一条数据后获取这条新增数据的 id 也是常见的业务需求。
PDO 提供了一个简单的获取最后插入行的 ID 的方法 PDO::lastInsertId() 供咱们使用。
基类添加 insertGetLastId() 方法:
public function insertGetLastId(array $data) { $this->insert($data); return $this->_pdo->lastInsertId(); }
测试:
$insert_data = [ 'username' => 'jack', 'age' => 18, ]; $lastId = $driver->table('test_table')->insertGetLastId($insert_data);
个体差别
然而,上述的 insertGetLastId() 方法在 PostgreSql 中并不奏效。PostgreSql 中,使用 PDO::lastInsertId() 获取结果须要传入正确的自增序列名 (PostgreSQL 中建立表时,若是使用 serial 类型,默认生成的自增序列名为:表名 + + 字段名 + + seq)。【1】
可是这个方式并很差用,由于访问 insertGetLastId() 方法时必须手动传入这个序列名称,这样 insertGetLastId() 方法对底层的依赖严重,好比当底层驱动从 postgresql 换到 mysql 时,须要更改上层应用。而咱们但愿不管是 mysql 仍是 postgresql,上层应用调用 insertGetLastId() 方法时是无差异的,即底层对上层透明。
为了解决这个问题,就须要用到 postgresql 的 returning 语法了。postgresql 中 insert、update 和 delete 操做都有一个可选的 returning 子句,能够指定最后执行的字段进行返回,返回的数据能够像 select 同样取结果。【2】
对于咱们返回最后插入行的 ID 的需求,只需 returning id 就好。
固然,基类的 insertGetLastId() 方法对于 postgresql 而言已经无效了,咱们在 Pgsql 驱动类中重写 insertGetLastId() 方法:
public function insertGetLastId(array $data) { // 构建语句字符串、绑定数据 $field_str = ''; $value_str = ''; foreach ($data as $key => $value) { $field_str .= ' '.self::_wrapRow($key).','; $plh = self::_getPlh(); $this->_bind_params[$plh] = $value; $value_str .= ' '.$plh.','; } $field_str = rtrim($field_str, ','); $value_str = rtrim($value_str, ','); // 使用 returning 子句返回 id $this->_insert_str = ' ('.$field_str.') VALUES ('.$value_str.') RETURNING id '; // execute $this->_buildInsert(); $this->_execute(); // 使用 returning 子句后,能够像使用 SELECT 同样获取一个 returning 指定字段的结果集。 $result = $this->_pdoSt->fetch(PDO::FETCH_ASSOC); // 返回 id return $result['id']; }
OK,咱们再来测试看看,是否是就好用了呢?
作完 insert,update 就很简单了,不一样的是为了防止全局更新的失误发生,update 构造时强行要求使用 where 子句。
一样的,添加 _update_str 属性,修改 _reset() 函数:
protected $_update_str = ''; ... protected function _reset() { $this->_table = ''; $this->_prepare_sql = ''; $this->_cols_str = ' * '; $this->_where_str = ''; $this->_orderby_str = ''; $this->_groupby_str = ''; $this->_having_str = ''; $this->_join_str = ''; $this->_limit_str = ''; $this->_insert_str = ''; $this->_update_str = ''; $this->_bind_params = []; }
构造 update 语句的方法:
protected function _buildUpdate() { $this->_prepare_sql = 'UPDATE '.$this->_table.$this->_update_str.$this->_where_str; }
基类中添加 update() 方法:
public function update(array $data) { // 检测有没有设置 where 子句 if(empty($this->_where_str)) { throw new \InvalidArgumentException("Need where condition"); } // 构建语句、参数绑定 $this->_update_str = ' SET '; foreach ($data as $key => $value) { $plh = self::_getPlh(); $this->_bind_params[$plh] = $value; $this->_update_str .= ' '.self::_wrapRow($key).' = '.$plh.','; } $this->_update_str = rtrim($this->_update_str, ','); $this->_buildUpdate(); $this->_execute(); // 返回影响的行数 return $this->_pdoSt->rowCount(); }
更新数据示例:
$update_data = [ 'username' => 'lucy', 'age' => 22, ]; $results = $driver->table('test_table') ->where('username', 'jack') ->update($update_data);
相比 insert、update,delete 语句更为简单,只需 where 子句便可。和 update 同样,须要防止误操做删除全部数据。
构造 delete 语句的方法:
protected function _buildDelete() { $this->_prepare_sql = 'DELETE FROM '.$this->_table.$this->_where_str; }
基类中添加 delete() 方法:
public function delete() { // 检测有没有设置 where 子句 if(empty($this->_where_str)) { throw new \InvalidArgumentException("Need where condition"); } $this->_buildDelete(); $this->_execute(); return $this->_pdoSt->rowCount(); }
删除数据示例:
$results = $driver->table('test_table') ->where('age', 18) ->delete();
既然有了 DML 操做,那么就少不了事务。对于事务,咱们能够直接使用 PDO 提供的 PDO::beginTransaction()、PDO::commit()、PDO::rollBack()、PDO::inTransaction() 方法来实现。
基类添加 beginTrans() 方法:
// 开始事务 public function beginTrans() { try { return $this->_pdo->beginTransaction(); } catch (PDOException $e) { // 断线重连 if ($this->_isTimeout($e)) { $this->_closeConnection(); $this->_connect(); try { return $this->_pdo->beginTransaction(); } catch (PDOException $e) { throw $e; } } else { throw $e; } } }
注:由于 PDO::beginTransaction() 也是和 PDO::prepare() 同样会链接数据库的方法,因此须要作断线重连的操做。
commitTrans() 方法:
// 提交事务 public function commitTrans() { return $this->_pdo->commit(); }
rollBackTrans() 方法:
// 回滚事务 public function rollBackTrans() { if ($this->_pdo->inTransaction()) { // 若是已经开始了事务,则运行回滚操做 return $this->_pdo->rollBack(); } }
事务使用示例:
// 注册事务 $driver->beginTrans(); $results = $driver->table('test_table') ->where('age', 18) ->delete(); $driver->commitTrans(); // 确认删除 // 回滚事务 $driver->beginTrans(); $results = $driver->table('test_table') ->where('age', 18) ->delete(); $driver->rollBackTrans(); // 撤销删除
【1】PHP Manual - PDO::lastInsertId
【2】PostgreSQL Documentation - Returning Data From Modified Rows