You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
422 lines
13 KiB
PHTML
422 lines
13 KiB
PHTML
5 years ago
|
<?php
|
||
|
// +----------------------------------------------------------------------
|
||
|
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||
|
// +----------------------------------------------------------------------
|
||
|
// | Copyright (c) 2010 http://topthink.com All rights reserved.
|
||
|
// +----------------------------------------------------------------------
|
||
|
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||
|
// +----------------------------------------------------------------------
|
||
|
// | Author: liu21st <liu21st@gmail.com>
|
||
|
// +----------------------------------------------------------------------
|
||
|
namespace Think\Model;
|
||
|
use Think\Model;
|
||
|
/**
|
||
|
* MongoModel模型类
|
||
|
* 实现了ODM和ActiveRecords模式
|
||
|
*/
|
||
|
class MongoModel extends Model{
|
||
|
// 主键类型
|
||
|
const TYPE_OBJECT = 1;
|
||
|
const TYPE_INT = 2;
|
||
|
const TYPE_STRING = 3;
|
||
|
|
||
|
// 主键名称
|
||
|
protected $pk = '_id';
|
||
|
// _id 类型 1 Object 采用MongoId对象 2 Int 整形 支持自动增长 3 String 字符串Hash
|
||
|
protected $_idType = self::TYPE_OBJECT;
|
||
|
// 主键是否自增
|
||
|
protected $_autoinc = true;
|
||
|
// Mongo默认关闭字段检测 可以动态追加字段
|
||
|
protected $autoCheckFields = false;
|
||
|
// 链操作方法列表
|
||
|
protected $methods = array('table','order','auto','filter','validate');
|
||
|
|
||
|
/**
|
||
|
* 利用__call方法实现一些特殊的Model方法
|
||
|
* @access public
|
||
|
* @param string $method 方法名称
|
||
|
* @param array $args 调用参数
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function __call($method,$args) {
|
||
|
if(in_array(strtolower($method),$this->methods,true)) {
|
||
|
// 连贯操作的实现
|
||
|
$this->options[strtolower($method)] = $args[0];
|
||
|
return $this;
|
||
|
}elseif(strtolower(substr($method,0,5))=='getby') {
|
||
|
// 根据某个字段获取记录
|
||
|
$field = parse_name(substr($method,5));
|
||
|
$where[$field] =$args[0];
|
||
|
return $this->where($where)->find();
|
||
|
}elseif(strtolower(substr($method,0,10))=='getfieldby') {
|
||
|
// 根据某个字段获取记录的某个值
|
||
|
$name = parse_name(substr($method,10));
|
||
|
$where[$name] =$args[0];
|
||
|
return $this->where($where)->getField($args[1]);
|
||
|
}else{
|
||
|
E(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_'));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 获取字段信息并缓存 主键和自增信息直接配置
|
||
|
* @access public
|
||
|
* @return void
|
||
|
*/
|
||
|
public function flush() {
|
||
|
// 缓存不存在则查询数据表信息
|
||
|
$fields = $this->db->getFields();
|
||
|
if(!$fields) { // 暂时没有数据无法获取字段信息 下次查询
|
||
|
return false;
|
||
|
}
|
||
|
$this->fields = array_keys($fields);
|
||
|
foreach ($fields as $key=>$val){
|
||
|
// 记录字段类型
|
||
|
$type[$key] = $val['type'];
|
||
|
}
|
||
|
// 记录字段类型信息
|
||
|
if(C('DB_FIELDTYPE_CHECK')) $this->fields['_type'] = $type;
|
||
|
|
||
|
// 2008-3-7 增加缓存开关控制
|
||
|
if(C('DB_FIELDS_CACHE')){
|
||
|
// 永久缓存数据表信息
|
||
|
$db = $this->dbName?$this->dbName:C('DB_NAME');
|
||
|
F('_fields/'.$db.'.'.$this->name,$this->fields);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 写入数据前的回调方法 包括新增和更新
|
||
|
protected function _before_write(&$data) {
|
||
|
$pk = $this->getPk();
|
||
|
// 根据主键类型处理主键数据
|
||
|
if(isset($data[$pk]) && $this->_idType == self::TYPE_OBJECT) {
|
||
|
$data[$pk] = new \MongoId($data[$pk]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* count统计 配合where连贯操作
|
||
|
* @access public
|
||
|
* @return integer
|
||
|
*/
|
||
|
public function count(){
|
||
|
// 分析表达式
|
||
|
$options = $this->_parseOptions();
|
||
|
return $this->db->count($options);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 获取唯一值
|
||
|
* @access public
|
||
|
* @return array | false
|
||
|
*/
|
||
|
public function distinct($field, $where=array() ){
|
||
|
// 分析表达式
|
||
|
$this->options = $this->_parseOptions();
|
||
|
$this->options['where'] = array_merge((array)$this->options['where'], $where);
|
||
|
|
||
|
$command = array(
|
||
|
"distinct" => $this->options['table'],
|
||
|
"key" => $field,
|
||
|
"query" => $this->options['where']
|
||
|
);
|
||
|
|
||
|
$result = $this->command($command);
|
||
|
return isset($result['values']) ? $result['values'] : false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 获取下一ID 用于自动增长型
|
||
|
* @access public
|
||
|
* @param string $pk 字段名 默认为主键
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function getMongoNextId($pk=''){
|
||
|
if(empty($pk)) {
|
||
|
$pk = $this->getPk();
|
||
|
}
|
||
|
return $this->db->getMongoNextId($pk);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 新增数据
|
||
|
* @access public
|
||
|
* @param mixed $data 数据
|
||
|
* @param array $options 表达式
|
||
|
* @param boolean $replace 是否replace
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function add($data='',$options=array(),$replace=false) {
|
||
|
if(empty($data)) {
|
||
|
// 没有传递数据,获取当前数据对象的值
|
||
|
if(!empty($this->data)) {
|
||
|
$data = $this->data;
|
||
|
// 重置数据
|
||
|
$this->data = array();
|
||
|
}else{
|
||
|
$this->error = L('_DATA_TYPE_INVALID_');
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
// 分析表达式
|
||
|
$options = $this->_parseOptions($options);
|
||
|
// 数据处理
|
||
|
$data = $this->_facade($data);
|
||
|
if(false === $this->_before_insert($data,$options)) {
|
||
|
return false;
|
||
|
}
|
||
|
// 写入数据到数据库
|
||
|
$result = $this->db->insert($data,$options,$replace);
|
||
|
if(false !== $result ) {
|
||
|
$this->_after_insert($data,$options);
|
||
|
if(isset($data[$this->getPk()])){
|
||
|
return $data[$this->getPk()];
|
||
|
}
|
||
|
}
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
// 插入数据前的回调方法
|
||
|
protected function _before_insert(&$data,$options) {
|
||
|
// 写入数据到数据库
|
||
|
if($this->_autoinc && $this->_idType== self::TYPE_INT) { // 主键自动增长
|
||
|
$pk = $this->getPk();
|
||
|
if(!isset($data[$pk])) {
|
||
|
$data[$pk] = $this->db->getMongoNextId($pk);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function clear(){
|
||
|
return $this->db->clear();
|
||
|
}
|
||
|
|
||
|
// 查询成功后的回调方法
|
||
|
protected function _after_select(&$resultSet,$options) {
|
||
|
array_walk($resultSet,array($this,'checkMongoId'));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 获取MongoId
|
||
|
* @access protected
|
||
|
* @param array $result 返回数据
|
||
|
* @return array
|
||
|
*/
|
||
|
protected function checkMongoId(&$result){
|
||
|
if(is_object($result['_id'])) {
|
||
|
$result['_id'] = $result['_id']->__toString();
|
||
|
}
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
// 表达式过滤回调方法
|
||
|
protected function _options_filter(&$options) {
|
||
|
$id = $this->getPk();
|
||
|
if(isset($options['where'][$id]) && is_scalar($options['where'][$id]) && $this->_idType== self::TYPE_OBJECT) {
|
||
|
$options['where'][$id] = new \MongoId($options['where'][$id]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 查询数据
|
||
|
* @access public
|
||
|
* @param mixed $options 表达式参数
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function find($options=array()) {
|
||
|
if( is_numeric($options) || is_string($options)) {
|
||
|
$id = $this->getPk();
|
||
|
$where[$id] = $options;
|
||
|
$options = array();
|
||
|
$options['where'] = $where;
|
||
|
}
|
||
|
// 分析表达式
|
||
|
$options = $this->_parseOptions($options);
|
||
|
$result = $this->db->find($options);
|
||
|
if(false === $result) {
|
||
|
return false;
|
||
|
}
|
||
|
if(empty($result)) {// 查询结果为空
|
||
|
return null;
|
||
|
}else{
|
||
|
$this->checkMongoId($result);
|
||
|
}
|
||
|
$this->data = $result;
|
||
|
$this->_after_find($this->data,$options);
|
||
|
return $this->data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 字段值增长
|
||
|
* @access public
|
||
|
* @param string $field 字段名
|
||
|
* @param integer $step 增长值
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public function setInc($field,$step=1) {
|
||
|
return $this->setField($field,array('inc',$step));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 字段值减少
|
||
|
* @access public
|
||
|
* @param string $field 字段名
|
||
|
* @param integer $step 减少值
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public function setDec($field,$step=1) {
|
||
|
return $this->setField($field,array('inc','-'.$step));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 获取一条记录的某个字段值
|
||
|
* @access public
|
||
|
* @param string $field 字段名
|
||
|
* @param string $spea 字段数据间隔符号
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function getField($field,$sepa=null) {
|
||
|
$options['field'] = $field;
|
||
|
$options = $this->_parseOptions($options);
|
||
|
if(strpos($field,',')) { // 多字段
|
||
|
if(is_numeric($sepa)) {// 限定数量
|
||
|
$options['limit'] = $sepa;
|
||
|
$sepa = null;// 重置为null 返回数组
|
||
|
}
|
||
|
$resultSet = $this->db->select($options);
|
||
|
if(!empty($resultSet)) {
|
||
|
$_field = explode(',', $field);
|
||
|
$field = array_keys($resultSet[0]);
|
||
|
$key = array_shift($field);
|
||
|
$key2 = array_shift($field);
|
||
|
$cols = array();
|
||
|
$count = count($_field);
|
||
|
foreach ($resultSet as $result){
|
||
|
$name = $result[$key];
|
||
|
if(2==$count) {
|
||
|
$cols[$name] = $result[$key2];
|
||
|
}else{
|
||
|
$cols[$name] = is_null($sepa)?$result:implode($sepa,$result);
|
||
|
}
|
||
|
}
|
||
|
return $cols;
|
||
|
}
|
||
|
}else{
|
||
|
// 返回数据个数
|
||
|
if(true !== $sepa) {// 当sepa指定为true的时候 返回所有数据
|
||
|
$options['limit'] = is_numeric($sepa)?$sepa:1;
|
||
|
} // 查找符合的记录
|
||
|
$result = $this->db->select($options);
|
||
|
if(!empty($result)) {
|
||
|
if(1==$options['limit']) {
|
||
|
$result = reset($result);
|
||
|
return $result[$field];
|
||
|
}
|
||
|
foreach ($result as $val){
|
||
|
$array[] = $val[$field];
|
||
|
}
|
||
|
return $array;
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 执行Mongo指令
|
||
|
* @access public
|
||
|
* @param array $command 指令
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function command($command, $options=array()) {
|
||
|
$options = $this->_parseOptions($options);
|
||
|
return $this->db->command($command, $options);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 执行MongoCode
|
||
|
* @access public
|
||
|
* @param string $code MongoCode
|
||
|
* @param array $args 参数
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function mongoCode($code,$args=array()) {
|
||
|
return $this->db->execute($code,$args);
|
||
|
}
|
||
|
|
||
|
// 数据库切换后回调方法
|
||
|
protected function _after_db() {
|
||
|
// 切换Collection
|
||
|
$this->db->switchCollection($this->getTableName(),$this->dbName?$this->dbName:C('db_name'));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 得到完整的数据表名 Mongo表名不带dbName
|
||
|
* @access public
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getTableName() {
|
||
|
if(empty($this->trueTableName)) {
|
||
|
$tableName = !empty($this->tablePrefix) ? $this->tablePrefix : '';
|
||
|
if(!empty($this->tableName)) {
|
||
|
$tableName .= $this->tableName;
|
||
|
}else{
|
||
|
$tableName .= parse_name($this->name);
|
||
|
}
|
||
|
$this->trueTableName = strtolower($tableName);
|
||
|
}
|
||
|
return $this->trueTableName;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 分组查询
|
||
|
* @access public
|
||
|
* @return string
|
||
|
*/
|
||
|
public function group($key, $init, $reduce, $option=array()) {
|
||
|
$option = $this->_parseOptions($option);
|
||
|
|
||
|
//合并查询条件
|
||
|
if(isset($option['where']))
|
||
|
$option['condition'] = array_merge((array)$option['condition'], $option['where']);
|
||
|
|
||
|
return $this->db->group($key, $init, $reduce, $option);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 返回Mongo运行错误信息
|
||
|
* @access public
|
||
|
* @return json
|
||
|
*/
|
||
|
public function getLastError(){
|
||
|
return $this->db->command(array('getLastError'=>1));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 返回指定集合的统计信息,包括数据大小、已分配的存储空间和索引的大小
|
||
|
* @access public
|
||
|
* @return json
|
||
|
*/
|
||
|
public function status(){
|
||
|
$option = $this->_parseOptions();
|
||
|
return $this->db->command(array('collStats'=>$option['table']));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 取得当前数据库的对象
|
||
|
* @access public
|
||
|
* @return object
|
||
|
*/
|
||
|
public function getDB(){
|
||
|
return $this->db->getDB();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 取得集合对象,可以进行创建索引等查询
|
||
|
* @access public
|
||
|
* @return object
|
||
|
*/
|
||
|
public function getCollection(){
|
||
|
return $this->db->getCollection();
|
||
|
}
|
||
|
}
|