超轻的 PHP 数据库工具包(2)
一个 SQL 构造器,用于构造 SQL 语句。配合 TableMetadata(表元信息)使用,简单的 SQL 基本能应付了。 br / 不过,这个组件依赖上次分享的 a href=http://www.oschina.net/code/snippet_74928_4038 rel=nof
一个 SQL 构造器,用于构造 SQL 语句。配合 TableMetadata(表元信息)使用,简单的 SQL 基本能应付了。
不过,这个组件依赖上次分享的 QuickQuery ……
这货其实是为一个 DataMapper 而设计的,但是 DataMapper 尚未写完,所以先把这个送出来了……的确,单独使用也可以,就是挺丑的。
不过,这个组件依赖上次分享的 QuickQuery ……
这货其实是为一个 DataMapper 而设计的,但是 DataMapper 尚未写完,所以先把这个送出来了……的确,单独使用也可以,就是挺丑的。
// 在框架的前端控制器初始化时,注册数据表元信息
new TableMetadata('Tags', array(
'id' => 'TagID',
'word' => 'TagWord',
'created' => 'CreatedDateTime'
));
new TableMetadata('Users', array(
'id' => 'UserID',
'name' => 'UserName',
'password' => 'Password',
'created' => 'CreatedDateTime'
));
new TableMetadata('Topics', array(
'id' => 'TopicID',
'title' => 'TopicTitle',
'content' => 'TopicContent',
'created' => 'CreatedDateTime',
'creater' => 'CreaterUserID',
'reply_to' => 'ReplyToTopicID'
));
new TableMetadata('Topics_Tags', array(
'topic_id' => 'TopicID',
'tag_id' => 'TagID'
));
// 使用,这里测试了 select,delete、update 神马的也同理
$query = new QuickQuery($conn);
$user = TableMetadata::getInstance('Users');
$topic = TableMetadata::getInstance('Topics');
$user->select('id', 'name', 'password', 'created')
->join(array($topic['title']), 'Topics', "${topic['creater']} = ${user['id']}")
->where("${user['id']} > :lt_id AND ${user['id']} < :st_id", array(':lt_id', ':st_id'))
->prepare($query)
->execute(array(':lt_id'=>5, ':st_id'=8));
// 上面也可以不执行 prepare,而执行 count,返回记录条数
foreach($query as $row) {
var_dump($row);
}
2. [代码]Persistence/DbAccess/QueryBuilder.php 跳至 [1] [2] [3] [全屏预览]
<?php
namespace Persistence\DbAccess;
use Exception, InvalidArgumentException, BadMethodCallException;
/**
* 查询构造器,以对象操作的方式构造查询 SQL
*
* @version 0.1
* @author tonyseek
* @link http://stu.szu.edu.cn
* @license http://www.opensource.org/licenses/mit-license.html
* @copyright StuCampus Development Team, Shenzhen University
*
*/
class QueryBuilder
{
const MODE_SELECT = 1;
const MODE_INSERT = 2;
const MODE_UPDATE = 3;
const MODE_DELETE = 4;
const JOIN_INNER = 'INNER';
const JOIN_LEFT = 'LEFT';
const JOIN_RIGHT = 'RIGHT';
const JOIN_FULL = 'FULL';
const ORDER_ASC = 'ASC';
const ORDER_DESC = 'DESC';
// 可用操作模式
static private $enableMode = array(
self::MODE_DELETE,
self::MODE_INSERT,
self::MODE_SELECT,
self::MODE_UPDATE
);
private $mode = null; // 操作模式
private $columns = array(); // 字段
private $params = array(); // 查询参数
private $table = ''; // 表
private $where = array();
private $order = array();
private $limitSize = null;
private $limitOffset = null;
private $join = array();
private $hasUsedPerfix = false;
/**
* 构造查询构造器
*
* @param array $columns 字段,格式为"字段key=>字段字符串形式"
* @param string $table 涉及的表
* @param int $mode 操作模式(select、update...)
*/
public function __construct(array $columns, $table, $mode)
{
// 参数有效性检查
if (!in_array($mode, self::$enableMode)) {
throw new InvalidArgumentException('参数 $mode 无效');
}
// 添加引号
self::quoteField($table);
array_walk($columns, array(get_class($this), 'quoteField')); // 添加引号
$this->columns = $columns;
$this->table = $table;
$this->mode = $mode;
}
/**
* 根据字段 key,从构造器中删除备选字段
*
* @param array $columns 字段 key 集合
*/
public function unsetColumns(array $columns)
{
foreach($columns as $unsetKey) {
if (!isset($this->columns[$unsetKey])) {
throw new InvalidArgumentException("将删除的备选字段[{$unsetKey}]不存在");
}
// 直接删除
unset($this->columns[$unsetKey]);
}
}
/**
* 批量调用 setParam
*
* @param array $params
* @return \Persistence\DbAccess\QueryBuilder
*/
public function setParams(array $params)
{
$this->params = array_merge($this->params, $params);
return $this;
}
/**
* 通过 WHERE 筛选
*
* @param string $statement WHERE 子句
* @param array $params 涉及的参数
* @return \Persistence\DbAccess\QueryBuilder
*/
public function where($statement, array $params = array())
{
$this->where[] = $statement;
$this->setParams($params);
return $this;
}
/**
* 通过 Order By 排序
*
* @param string $column 依据字段
* @param string $orderBy 排序模式
* @return \Persistence\DbAccess\QueryBuilder
*/
public function orderBy($column, $orderBy)
{
self::quoteField($column);
$this->order[] = "{$column} {$orderBy}";
return $this;
}
/**
* 通过 Limit 限制结果片段
*
* @param int $size 片段尺寸
* @param int $offset 片段开始点偏移
* @return \Persistence\DbAccess\QueryBuilder
*/
public function limit($size, $offset = 0)
{
$this->limitSize = $size;
$this->limitOffset = $offset;
return $this;
}
/**
* 通过 Join 连接其他表
*
* @param array $columns 得到的字段
* @param string $table 连接的右表
* @param string $on 连接点(条件)
* @param string $mode 连接模式(SQL 语法)
* @return \Persistence\DbAccess\QueryBuilder
*/
public function join(array $columns, $table, $on, $mode=self::JOIN_INNER)
{
if ($this->mode != self::MODE_SELECT) {
throw new BadMethodCallException('只有 MODE_SELECT 下才能使用 JOIN');
}
// 给主表添加前缀防止重名
$this->usePerfix();
// 添加字段
$perfix = $table;
self::quoteField($table);
foreach($columns as $key=>$field) {
$key = "{$perfix}_{$key}"; // 给 key 添加前缀
self::quoteField($field);
$this->columns[$key] = "{$table}.{$field}";
}
$this->join[$perfix] = array(
'on' => $on,
'mode' => $mode,
'table' => $table,
);
return $this;
}
/**
* 构造 QuickQuery 对象
*
* @param QuickQuery $query
* @return \Persistence\DbAccess\QuickQuery
*/
public function prepare(QuickQuery $query)
{
$query->prepare($this->buildStatement())
->setParamsCheck($this->params);
return $query;
}
/**
* 统计查询结果数目
*
* @param QuickQuery $query
* @param string $column[default='*']
* @param array $params[default=null]
* @throws \PDOException
* @return int
*/
public function count(QuickQuery $query, array $params=array(), $column='*')
{
if ($this->mode != self::MODE_SELECT) {
throw new BadMethodCallException('只有 MODE_SELECT 可以执行 count');
}
$query = clone $query;
$query->prepare($this->buildCount($column))
->setParamsCheck($this->params)
->execute($params);
$count = $query->getStatement()->fetch();
$count = $count['count'];
return $count;
}
/**
* 构造 SQL 语句
*
* @return string
*/
public function buildStatement()
{
switch($this->mode) {
case self::MODE_SELECT:
return $this->buildSelect();
case self::MODE_INSERT:
return $this->buildInsert();
case self::MODE_UPDATE:
return $this->buildUpdate();
case self::MODE_DELETE:
return $this->buildDelete();
}
}
// 构造 Select 语句
private function buildSelect()
{
$columns = implode(', ', $this->columns);
$stmt = "SELECT {$columns} FROM {$this->table}";
foreach($this->join as $join) {
$stmt = "{$stmt} {$join['mode']} JOIN {$join['table']} ON {$join['on']}";
}
if (count($this->where)) {
$where = implode(' AND ', $this->where);
$stmt = "{$stmt} WHERE {$where}";
}
if ($this->limitSize) {
$stmt = "{$stmt} LIMIT {$this->limitSize} OFFSET {$this->limitOffset}";
}
if (count($this->order)) {
$order = implode(', ', $this->order);
$stmt = "{$stmt} ORDER BY {$order}";
}
return $stmt;
}
// 构造 Insert 语句
private function buildInsert()
{
$columns = implode(', ', $this->columns);
// 构造 value 语句段
$paramFormat = function($x){ return ":{$x}"; };
$values = array_map($paramFormat, array_keys($this->columns));
$this->setParams($values); // 设置参数表
$values = implode(', ', $values);
$stmt = "INSERT INTO {$this->table} ({$columns}) VALUES ($values)";
return $stmt;
}
// 构造 Update 语句
private function buildUpdate()
{
// 构造 Update 的 SET 语句
$setters = array_map(function($key, $value){
return "{$value}=:{$key}";
}, array_keys($this->columns), $this->columns);
$setters = implode(', ', $setters);
// 设置参数表
$paramFormat = function($x){ return ":{$x}"; };
$this->setParams(array_map($paramFormat, array_keys($this->columns)));
// 如果使用了 order 或 limit 则改为嵌套子查询的语句
if (count($this->order) || count($this->limitSize)) {
return $this->buildUpdateByNest($setters);
}
$stmt = "UPDATE {$this->table} SET {$setters}";
if (count($this->where)) {
$where = implode(' AND ', $this->where);
$stmt = "{$stmt} WHERE {$where}";
} else {
throw new Exception('不允许使用没有 Where 子句的 Update');
}
return $stmt;
}
// 通过嵌套子查询的 Update 语句
private function buildUpdateByNest($setters)
{
$stmt = $this->buildSelect();
$columns = implode(', ', $this->columns);
$stmt = "UPDATE {$this->table} SET {$setters} WHERE ({$columns}) IN ({$stmt})";
return $stmt;
}
// 构造 Delete 语句
private function buildDelete()
{
if (!count($this->where)) {
throw new Exception('不允许使用没有 Where 子句的 Delete');
}
$where = implode(' AND ', $this->where);
$stmt = "DELETE FROM {$this->table} WHERE {$where}";
return $stmt;
}
// 构造 Count 查询语句
private function buildCount($column)
{
if ($column != '*') {
self::quoteField($column);
}
$stmt = "SELECT count({$column}) as count FROM {$this->table}";
foreach($this->join as $join) {
$stmt = "{$stmt} {$join['mode']} JOIN {$join['table']} ON {$join['on']}";
}
if (count($this->where)) {
$where = implode(' AND ', $this->where);
$stmt = "{$stmt} WHERE {$where}";
}
return $stmt;
}
// 给表添加命名前缀
private function usePerfix()
{
if (!$this->hasUsedPerfix) {
$mainTable = $this->table;
$this->columns = array_map(function($x) use($mainTable) {
return "{$mainTable}.{$x}";
}, $this->columns);
$this->hasUsedPerfix = true;
}
}
// 给字段添加引号
static private function quoteField(&$field)
{
$field = '"' . $field . '"';
}
}
3. [代码]Persistence/DbAccess/TableMetadata .php 跳至 [1] [2] [3] [全屏预览]
<?php
namespace Persistence\DbAccess;
use ArrayAccess, InvalidArgumentException, BadMethodCallException;
/**
* 数据表元信息(MetaData)
*
* @version 0.1
* @author tonyseek
* @link http://stu.szu.edu.cn
* @license http://www.opensource.org/licenses/mit-license.html
* @copyright StuCampus Development Team, Shenzhen University
*/
class TableMetadata implements ArrayAccess
{
static private $instances = array();
private $columns = array();
private $tableName = '';
/**
* @param string $tableName 数据表的名称
* @param array $metadata 元信息关联数组, 格式keyName=>codeName
*/
public function __construct($tableName, array $metadata)
{
$this->tableName = $tableName;
$this->columns = $metadata;
self::$instances[$tableName] = $this;
}
/**
* 获取一个已经被实例化的元信息
*
* @param string $tableName
* @return \Persistence\DbAccess\TableMetadata
*/
static public function getInstance($tableName)
{
if (!isset(self::$instances[$tableName])) {
throw new InvalidArgumentException("元信息中不存在 {$tableName} 表");
}
return self::$instances[$tableName];
}
/**
* 获取字段的字符串 CodeName
*
* @param string $keyName
* @param bool $withTableName
* @param bool $quote
* @return string
*/
public function getColumn($keyName, $withTableName=true, $quote=true)
{
if (!isset($this->columns[$keyName])) {
throw new InvalidArgumentException("元信息中不存在 {$keyName} 字段");
}
$quote = $quote ? '"' : '';
$codeName = "{$quote}{$this->columns[$keyName]}{$quote}";
if ($withTableName) {
$table = "{$quote}{$this->tableName}{$quote}";
$codeName = "{$table}.{$codeName}";
}
return $codeName;
}
/**
* 批量获取字段的字符串 CodeName
*
* @param array $keyNames
* @param bool $withTableName
* @param bool $quote
* @return array
*/
public function getColumns(array $keyNames, $withTableName=true, $quote=true)
{
$columns = array();
foreach($keyNames as $keyName) {
$columns[$keyName] = $this->getColumn($keyName, $withTableName, $quote);
}
return $columns;
}
/**
* 返回不含引号的数据表名称
*
* @return string
*/
public function getTableName()
{
return $this->tableName;
}
/**
* @see ArrayAccess::offsetExists()
*/
public function offsetExists($offset)
{
return isset($this->columns[$offset]);
}
/**
* @see ArrayAccess::offsetGet()
*/
public function offsetGet($offset)
{
return sprintf('"%s"."%s"', $this->tableName, $this->columns[$offset]);
}
/**
* @see ArrayAccess::offsetSet()
* @throws BadMethodCallException
*/
public function offsetSet($offset, $value)
{
throw new BadMethodCallException('TableMetadata 是只读对象');
}
/**
* @see ArrayAccess::offsetUnset()
* @throws BadMethodCallException
*/
public function offsetUnset($offset)
{
throw new BadMethodCallException('TableMetadata 是只读对象');
}
/**
* 由元信息构造 select 查询
*
* @param array args
* @return \Persistence\DbAccess\QueryBuilder
*/
public function select()
{
return $this->buildQuery(func_get_args(), QueryBuilder::MODE_SELECT);
}
/**
* 由元信息构造 update 查询
*
* @param array args
* @return \Persistence\DbAccess\QueryBuilder
*/
public function update()
{
return $this->buildQuery(func_get_args(), QueryBuilder::MODE_UPDATE);
}
/**
* 由元信息构造 insert 查询
*
* @return \Persistence\DbAccess\QueryBuilder
*/
public function insert()
{
return $this->buildQuery(array(), QueryBuilder::MODE_INSERT);
}
/**
* 由元信息构造 delete 查询
*
* @return \Persistence\DbAccess\QueryBuilder
*/
public function delete()
{
return $this->buildQuery(array(), QueryBuilder::MODE_DELETE);
}
public function joinInto(QueryBuilder $builder, array $keys, $on, $mode=QueryBuilder::JOIN_INNER)
{
$columns = array();
foreach($keys as $key) {
$columns[$key] = $this->columns[$key];
}
$builder->join($columns, $this->tableName, $on, $mode);
}
public function __toString()
{
return '"' . $this->tableName . '"';
}
// 构造字段范围内的 QueryBuilder
private function buildQuery(array $range, $mode)
{
$columns = count($range) ? array() : $this->columns;
foreach($range as $keyName) {
$columns[$keyName] = $this->columns[$keyName];
}
if ($mode != QueryBuilder::MODE_SELECT && isset($columns['id'])) {
unset($columns['id']);
}
return new QueryBuilder($columns, $this->tableName, $mode);
}
}
精彩图集
精彩文章






