一、前言
因为使用习惯了TP和Laravel的数据库操作方式,所以又重新在代码写SQL操作会不太习惯,且代码看着也不直观,于是根据MixPHP的数据库操作方式进行了二次封装。
二、数据库操作基础类
<?php
namespace apps\common\models\database;
use mix\facades\PDO;
abstract class BaseModel
{
protected $table; // 当前执行数据表名称
protected $field = ''; // 当前查询的字段
protected $join = ''; // 关联sql语句
protected $where = ''; // 条件sql语句
protected $order = ''; // 排序sql语句
protected $group = ''; // 分组sql语句
protected $having = ''; // 分组条件sql语句
protected $limit = ''; // 分页sql语句
protected $param = []; // 数据绑定数组
public function __construct()
{
$this->setTable();
}
private function setTable()
{
$this->table = $this->table();
}
public abstract function table();
public function field(string $field)
{
$this->field = $field;
return $this;
}
/**
* 组建关连表查询片段sql语句
* @param string $relaTabel 关联表名称
* @param string $param 关联表关联字段
* @param string $relaParam 当前表外键字段
* @param string $alias 连表别名
* @param string $joinType 连表连表查询类别
*/
public function join(string $relaTable, string $param, string $relaParam, string $alias = '', string $type = 'LEFT')
{
$relaTable = $alias ? "`{$relaTable}` AS `{$alias}`" : "`{$relaTable}`";
$param = $alias ? "`{$alias}`.{$param}" : "{$relaTable}.{$param}";
if ($this->join) {
$this->join .= " {$type} JOIN {$relaTable} ON {$param} = `" . $this->table . "`.{$relaParam}";
} else {
$this->join = " {$type} JOIN {$relaTable} ON {$param} = `" . $this->table . "`.{$relaParam}";
}
return $this;
}
public function where(array $where)
{
if (empty($where)) return $this;
$map = [];
$param = [];
// 判断1维 or 2维数组
if (count($where) == count($where, 1)) {
foreach ($where as $k => $v) {
$map[] = "`" . $this->table . "`.`{$k}` = :{$k}";
}
$param = $where;
} else {
foreach ($where as $k => $v) {
$map[] = "`" . $this->table . "`.`{$v[0]}` {$v[1]} :{$v[0]}";
$param[$v[0]] = $v[2];
}
}
if ($this->where) {
$this->where .= ' AND ' . implode(' AND ', $map);
} else {
$this->where = " WHERE " . implode(' AND ', $map);
}
$this->param = array_merge($this->param, $param);
return $this;
}
public function orWhere(array $where)
{
if ($this->where)
app()->dump("orWhere方法必须在where方法前调用!", true);
if (empty($where)) return $this;
$map = [];
$param = [];
// 判断1维 or 2维数组
if (count($where) == count($where, 1)) {
foreach ($where as $k => $v) {
$map[] = "`" . $this->table . "`.`{$k}` = :{$k}";
}
$param = $where;
} else {
foreach ($where as $k => $v) {
$map[] = "`" . $this->table . "`.`{$v[0]}` {$v[1]} :{$v[0]}";
$param[$v[0]] = $v[2];
}
}
$this->where = ' WHERE (' . implode(' OR ', $map) . ')';
$this->param = array_merge($this->param, $param);
return $this;
}
/**
* $where :
['register_time', '1', '12121212']
or
[
['register_time', '1', '12121212'],
['register_time', '1', '12121212']
]
*/
public function betweenWhere(array $where)
{
if (empty($where)) return $this;
$map = [];
$param = [];
// 判断1维 or 2维数组
if (count($where) == count($where, 1)) {
$map[] = "`" . $this->table . "`.`{$where[0]}` BETWEEN :{$where[0]}1 AND :{$where[0]}2";
$param = ["{$where[0]}1"=>$where[1], "{$where[0]}2"=>$where[2]];
} else {
foreach ($where as $k => $v) {
$map[] = "`" . $this->table . "`.`{$v[0]}` BETWEEN :{$v[0]}1 AND :{$v[0]}2";
$param["{$v[0]}1"] = $v[1];
$param["{$v[0]}2"] = $v[2];
}
}
if ($this->where) {
$this->where .= ' AND ' . implode(' AND ', $map);
} else {
$this->where = " WHERE " . implode(' AND ', $map);
}
$this->param = array_merge($this->param, $param);
return $this;
}
public function whereIn(string $column, array $arr, $status = 'AND')
{
$this->where = $this->where ? " {$status} " : " WHERE ";
$this->where .= "`" . $this->table . "`.`{$column}` IN (" . implode(',', $arr) . ")";
return $this;
}
public function order(string $order)
{
$this->order = " ORDER BY " . $order;
return $this;
}
public function group(string $group)
{
$this->group = " GROUP BY " . $group;
return $this;
}
public function having(string $having)
{
$this->having = " HAVING " . $having;
return $this;
}
public function page(int $page, int $perPage, int $status = 1)
{
$this->limit = ' LIMIT :offset,:rows';
$this->param = array_merge($this->param, ['offset'=>(($page-1) * $perPage), 'rows'=>$perPage]);
return $this->select($status);
}
public function selectSql()
{
$sql = "SELECT ";
$sql .= $this->field ? $this->field : '*';
$sql .= ' FROM `' . $this->table . '`';
$sql .= $this->join ? $this->join : '';
$sql .= $this->where ? $this->where : '';
$sql .= $this->group ? $this->group : '';
$sql .= $this->having ? $this->having : '';
$sql .= $this->order ? $this->order : '';
$sql .= $this->limit ? $this->limit : '';
return $sql;
}
public function select($status = 1)
{
$list = PDO::createCommand($this->selectSql())->bindParams($this->param)->queryAll();
if ($status) $this->clear();
return $list;
}
public function find(int $id = 0)
{
if ($id) $this->where(['id'=>$id]);
$info = PDO::createCommand($this->selectSql())->bindParams($this->param)->queryOne();
$this->clear();
return $info;
}
public function value($field, $id = 0)
{
if ($id) $this->where(['id'=>$id]);
$this->field("`{$field}`");
$info = PDO::createCommand($this->selectSql())->bindParams($this->param)->queryOne();
$this->clear();
return $info[$field];
}
public function count()
{
$sql = 'SELECT COUNT(*) FROM `' . $this->table . '`';
$sql .= $this->where ? $this->where : '';
$sql .= $this->group ? $this->group : '';
if(isset($this->param['rows'])) unset($this->param['rows']);
if(isset($this->param['offset'])) unset($this->param['offset']);
return PDO::createCommand($sql)->bindParams($this->param)->queryScalar();
}
public function insert(array $data)
{
PDO::insert($this->table, $data)->execute();
return PDO::getLastInsertId();
}
public function insertAll(array $data)
{
PDO::batchInsert($this->table, $data)->execute();
return PDO::getRowCount();
}
public function update(array $data, array $where, string $method = 'AND')
{
PDO::update($this->table, $data, $where, $method)->execute();
return PDO::getRowCount();
}
public function delete(array $where)
{
PDO::delete($this->table, $where)->execute();
return PDO::getRowCount();
}
public function getLastSql()
{
return PDO::getRawSql();
}
public function clear()
{
$this->field = '';
$this->join = '';
$this->where = '';
$this->group = '';
$this->having = '';
$this->order = '';
$this->limit = '';
$this->param = [];
return $this;
}
}
三、数据库基类的使用示例
1. 创建数据库操作层
一般我们会设置一个数据库操作层,此处架构在common公共模块下的models下,在models下创建database文件夹,将我们上一步创建好的数据库操作基类放在这个文件夹下,如:
2. 创建具体的数据表模型,如用户数据表操作模型,还是在数据库模型文件夹下apps/common/models/database/UserModel.php:
<?php
namespace apps\common\models\database;
class UserModel extends BaseModel
{
const TABLE = 'user';
public function table()
{
return self::TABLE;
}
}
3. 控制器内操作数据库
写接口的时候我们往往还需要一个验证层,这个MixPHP的作者也有考虑,每个应用中也对应有设计models文件夹对应验证模型层,而控制器中肯定会引入对应的验证器模型,且验证器中大多也需要操作数据库,所以数据库模型就直接选择在验证器模型中引入,具体操作如下:
(1)验证器,/apps/api/models/UserForm.php
<?php
namespace apps\api\models;
use mix\validators\Validator;
use apps\common\models\database\UserModel;
class UserForm extends Validator
{
// 实例化数据库操作模型
public function model()
{
return (new UserModel());
}
public function rules()
{
return [
'id' => ['call', 'callback' => [$this, 'mustBeId']],
'email' => ['email', 'length' => 10, 'minLength' => 3, 'maxLength' => 50],
'age' => ['integer', 'unsigned' => true, 'min' => 1, 'max' => 120]
];
}
public function scenarios()
{
return [
'index' => ['required' => ['id'], 'optional' => ['email', 'age']]
];
}
public function messages()
{
return [
'id.callback' => 'id参数错误'
];
}
// 自定义验证规则
public function mustBeId($id)
{
if (!$id || !$this->model()->find($id))
return false;
return true;
}
}
(2)控制器,/apps/api/controllers/UserController.php
<?php
namespace apps\api\controllers;
use mix\facades\Request;
use apps\api\models\UserForm; // 引入用户验证模型类
class UserController extends BaseController
{
protected $data;
protected $model;
protected $validate;
public function onConstruct()
{
parent::onConstruct();
// 实例化用户验证模型类
$this->validate = new UserForm();
// 实例化用户数据表操作模型类
$this->model = $this->validate->model();
// 获取接口参数
$this->data = Request::getRawBody();
}
protected function responseError($code, $msg)
{
return [
'code' => $code,
'msg' => $msg
];
}
protected function responseOk($data = [])
{
return [
'code' => 200,
'msg' => 'Ok',
'data' => $data
];
}
/**
* 接口参数验证公共方法
* @param $method 指定验证场景
* @return 返回所有获取的参数
*/
protected function vali($method)
{
$data = $this->data == '' ? [] : json_decode($this->jsonData, true);
if ($this->data && !$data)
return $this->responseError(-1, 'json参数错误');
$data = $data + Request::get() + Request::post() + Request::route();
$this->validate->attributes = $data;
if (!$this->validate->setScenario($method)->validate())
return $this->responseError(-1, $this->validate->getError());
return $data;
}
public function actionIndex()
{
$data = $this->vali('index');
if ($this->data && !$data)
return $this->responseError(-1, 'json参数错误');
$info = $this->model()->find($data['id']);
return responseOk($info);
}
}
注意:验证核心类 setScenario()函数需要修改,具体地址为:\vendor\mixstart\mixphp-framework\src\validators\Validator.php,修改如下:
// 设置当前场景 public function setScenario($scenario) { $scenarios = $this->scenarios(); if (!isset($scenarios[$scenario])) { throw new \mix\exceptions\ValidatorException("场景不存在:{$scenario}"); } if (!isset($scenarios[$scenario]['required'])) { throw new \mix\exceptions\ValidatorException("场景 {$scenario} 未定义 required 选项"); } if (!isset($scenarios[$scenario]['optional'])) { $scenarios[$scenario]['optional'] = []; } $this->_scenario = $scenarios[$scenario]; return $this; // 添加返回当前对象 }
到此,数据库操作生产流程基本完成,有疑问或者有优化建议的朋友欢迎留言告知。