<?php
|
require_once PHALAPI_ROOT . DIRECTORY_SEPARATOR . 'NotORM' . DIRECTORY_SEPARATOR . 'NotORM.php';
|
|
/**
|
* PhalApi_DB_NotORM 分布式的DB存储
|
*
|
* 基于NotORM的数据库操作,支持分布式
|
*
|
* - 可定义每个表的存储路由和规则,匹配顺序:
|
* 自定义区间匹配 -> 自定义缺省匹配 -> 默认区间匹配 -> 默认缺省匹配
|
* - 底层依赖NotORM实现数据库的操作
|
*
|
* <br>使用示例:<br>
|
```
|
* //需要提供以下格式的DB配置
|
* $config = array(
|
* //可用的DB服务器集群
|
* 'servers' => array(
|
* 'db_demo' => array(
|
* 'host' => 'localhost', //数据库域名
|
* 'name' => 'phalapi', //数据库名字
|
* 'user' => 'root', //数据库用户名
|
* 'password' => '', //数据库密码
|
* 'port' => '3306', //数据库端口
|
* 'charset' => 'UTF8', //数据库字符集
|
* ),
|
* ),
|
*
|
* //自定义表的存储路由
|
* 'tables' => array(
|
* '__default__' => array( //默认
|
* 'prefix' => 'tbl_',
|
* 'key' => 'id',
|
* 'map' => array(
|
* array('db' => 'db_demo'), //默认缺省
|
* array('start' => 0, 'end' => 2, 'db' => 'db_demo'), //默认区间
|
* ),
|
* ),
|
* 'demo' => array( //自定义
|
* 'prefix' => 'tbl_',
|
* 'key' => 'id',
|
* 'map' => array(
|
* array('db' => 'db_demo'), //自定义缺省
|
* array('start' => 0, 'end' => 2, 'db' => 'db_demo'), //定义区间
|
* ),
|
* ),
|
* ),
|
* );
|
*
|
* $notorm = new PhalApi_DB_NotORM($config);
|
*
|
* //根据ID对3取模的映射获取数据
|
* $rs = $notorm->demo_0->select('*')->where('id', 10)->fetch();
|
* $rs = $notorm->demo_1->select('*')->where('id', 11)->fetch();
|
```
|
*
|
* @property string table_name 数据库表名
|
*
|
* @package PhalApi\DB
|
* @link http://www.notorm.com/
|
* @license http://www.phalapi.net/license GPL 协议
|
* @link http://www.phalapi.net/
|
* @author dogstar <chanzonghuang@gmail.com> 2014-11-22
|
*/
|
|
class PhalApi_DB_NotORM /** implements PhalApi_DB */ {
|
|
/**
|
* @var NotORM $_notorms NotORM的实例池
|
*/
|
protected $_notorms = array();
|
|
/**
|
* @var PDO $_pdos PDO连接池,统一管理,避免重复连接
|
*/
|
protected $_pdos = array();
|
|
/**
|
* @var array $_configs 数据库配置
|
*/
|
protected $_configs = array();
|
|
/**
|
* @var boolean 是否开启调试模式,调试模式下会输出全部执行的SQL语句和对应消耗的时间
|
*/
|
protected $debug = FALSE;
|
|
/**
|
* @var boolean 是否保持原来数据库结果集中以主键为KEY的返回方式(默认不使用)
|
*/
|
protected $isKeepPrimaryKeyIndex = FALSE;
|
|
/**
|
* @param array $configs 数据库配置
|
* @param boolean $debug 是否开启调试模式
|
*/
|
public function __construct($configs, $debug = FALSE) {
|
$this->_configs = $configs;
|
|
$this->debug = $debug;
|
}
|
|
public function __get($name) {
|
$notormKey = $this->createNotormKey($name);
|
|
if (!isset($this->_notorms[$notormKey])) {
|
list($tableName, $suffix) = $this->parseName($name);
|
$router = $this->getDBRouter($tableName, $suffix);
|
|
$structure = new NotORM_Structure_Convention(
|
$router['key'], '%s_id', '%s', $router['prefix']);
|
$this->_notorms[$notormKey] = new NotORM($router['pdo'], $structure);
|
|
$this->_notorms[$notormKey]->debug = $this->debug;
|
$this->_notorms[$notormKey]->isKeepPrimaryKeyIndex = $this->isKeepPrimaryKeyIndex;
|
|
if ($router['isNoSuffix']) {
|
$name = $tableName;
|
}
|
}
|
|
return $this->_notorms[$notormKey]->$name;
|
}
|
|
public function __set($name, $value) {
|
foreach ($this->_notorms as $notorm) {
|
$notorm->$name = $value;
|
}
|
}
|
|
protected function createNotormKey($tableName) {
|
return '__' . $tableName . '__';
|
}
|
|
/**
|
* 解析分布式表名
|
* 表名 + ['_' + 数字后缀],如:user_0, user_1, ... user_100
|
* @param string $name
|
*/
|
protected function parseName($name) {
|
$tableName = $name;
|
$suffix = NULL;
|
|
$pos = strrpos($name, '_');
|
if ($pos !== FALSE) {
|
$tableId = substr($name, $pos + 1);
|
if (is_numeric($tableId)) {
|
$tableName = substr($name, 0, $pos);
|
$suffix = intval($tableId);
|
}
|
}
|
|
return array($tableName, $suffix);
|
}
|
|
/**
|
* 获取分布式数据库路由
|
* @param string $tableName 数据库表名
|
* @param string $suffix 分布式下的表后缀
|
* @return array 数据库配置
|
* @throws PhalApi_Exception_InternalServerError
|
*/
|
protected function getDBRouter($tableName, $suffix) {
|
$rs = array('prefix' => '', 'key' => '', 'pdo' => NULL, 'isNoSuffix' => FALSE);
|
|
$defaultMap = !empty($this->_configs['tables']['__default__'])
|
? $this->_configs['tables']['__default__'] : array();
|
$tableMap = !empty($this->_configs['tables'][$tableName])
|
? $this->_configs['tables'][$tableName] : $defaultMap;
|
|
if (empty($tableMap)) {
|
throw new PhalApi_Exception_InternalServerError(
|
T('No table map config for {tableName}', array('tableName' => $tableName))
|
);
|
}
|
|
$dbKey = NULL;
|
$dbDefaultKey = NULL;
|
if (!isset($tableMap['map'])) {
|
$tableMap['map'] = array();
|
}
|
foreach ($tableMap['map'] as $map) {
|
$isMatch = FALSE;
|
|
if ((isset($map['start']) && isset($map['end']))) {
|
if ($suffix !== NULL && $suffix >= $map['start'] && $suffix <= $map['end']) {
|
$isMatch = TRUE;
|
}
|
} else {
|
$dbDefaultKey = $map['db'];
|
if ($suffix === NULL) {
|
$isMatch = TRUE;
|
}
|
}
|
|
if ($isMatch) {
|
$dbKey = isset($map['db']) ? trim($map['db']) : NULL;
|
break;
|
}
|
}
|
//try to use default map if no perfect match
|
if ($dbKey === NULL) {
|
$dbKey = $dbDefaultKey;
|
$rs['isNoSuffix'] = TRUE;
|
}
|
|
if ($dbKey === NULL) {
|
throw new PhalApi_Exception_InternalServerError(
|
T('No db router match for {tableName}', array('tableName' => $tableName))
|
);
|
}
|
|
$rs['pdo'] = $this->getPdo($dbKey);
|
$rs['prefix'] = isset($tableMap['prefix']) ? trim($tableMap['prefix']) : '';
|
$rs['key'] = isset($tableMap['key']) ? trim($tableMap['key']) : 'id';
|
|
return $rs;
|
}
|
|
/**
|
* 获取 PDO连接
|
* @param string $dbKey 数据库表名唯一KEY
|
* @return PDO
|
*/
|
protected function getPdo($dbKey) {
|
if (!isset($this->_pdos[$dbKey])) {
|
$dbCfg = isset($this->_configs['servers'][$dbKey])
|
? $this->_configs['servers'][$dbKey] : array();
|
|
if (empty($dbCfg)) {
|
throw new PhalApi_Exception_InternalServerError(
|
T('no such db:{db} in servers', array('db' => $dbKey)));
|
}
|
|
try {
|
$this->_pdos[$dbKey] = $this->createPDOBy($dbCfg);
|
} catch (PDOException $ex) {
|
//异常时,接口异常返回,并隐藏数据库帐号信息
|
$errorMsg = T('can not connect to database: {db}', array('db' => $dbKey));
|
if (DI()->debug) {
|
$errorMsg = T('can not connect to database: {db}, code: {code}, cause: {msg}',
|
array('db' => $dbKey, 'code' => $ex->getCode(), 'msg' => $ex->getMessage()));
|
}
|
throw new PhalApi_Exception_InternalServerError($errorMsg);
|
}
|
}
|
|
return $this->_pdos[$dbKey];
|
}
|
|
/**
|
* 针对MySQL的PDO链接,如果需要采用其他数据库,可重载此函数
|
* @param array $dbCfg 数据库配置
|
* @return PDO
|
*/
|
protected function createPDOBy($dbCfg) {
|
$dsn = sprintf('mysql:dbname=%s;host=%s;port=%d',
|
$dbCfg['name'],
|
isset($dbCfg['host']) ? $dbCfg['host'] : 'localhost',
|
isset($dbCfg['port']) ? $dbCfg['port'] : 3306
|
);
|
$charset = isset($dbCfg['charset']) ? $dbCfg['charset'] : 'UTF8';
|
|
$pdo = new PDO(
|
$dsn,
|
$dbCfg['user'],
|
$dbCfg['password']
|
);
|
$pdo->exec("SET NAMES '{$charset}'");
|
|
return $pdo;
|
}
|
|
/**
|
* 断开数据库链接
|
*/
|
public function disconnect() {
|
foreach ($this->_pdos as $dbKey => $pdo) {
|
$this->_pdos[$dbKey] = NULL;
|
unset($this->_pdos[$dbKey]);
|
}
|
|
foreach ($this->_notorms as $notormKey => $notorm) {
|
$this->_notorms[$notormKey] = NULL;
|
unset($this->_notorms[$notormKey]);
|
}
|
}
|
|
/**
|
* 为历史修改埋单:保持原来数据库结果集中以主键为KEY的返回方式
|
*
|
* - PhalSpi 1.3.1 及以下版本才需要用到此切换动作
|
* - 涉及影响的数据库操作有:fetchAll()/fetchRows()等
|
*
|
* @return PhalApi_DB_NotORM
|
*/
|
public function keepPrimaryKeyIndex() {
|
$this->isKeepPrimaryKeyIndex = TRUE;
|
return $this;
|
}
|
|
/** ------------------ 事务操作 ------------------ **/
|
|
/**
|
* 开启数据库事务
|
* @param string $whichDB 指定数据库标识
|
* @return NULL
|
*/
|
public function beginTransaction($whichDB) {
|
$this->getPdo($whichDB)->beginTransaction();
|
}
|
|
/**
|
* 提交数据库事务
|
* @param string $whichDB 指定数据库标识
|
* @return NULL
|
*/
|
public function commit($whichDB) {
|
$this->getPdo($whichDB)->commit();
|
}
|
|
/**
|
* 回滚数据库事务
|
* @param string $whichDB 指定数据库标识
|
* @return NULL
|
*/
|
public function rollback($whichDB) {
|
$this->getPdo($whichDB)->rollback();
|
}
|
}
|