tp已经支持mongodb的数据库,下面是对mongo在tp上的配置和使用做一个详细的说明。
(1)tp3.2.2版本中的/think/Model/mongoModel.class.php的原来的类是存在bug的。我们需要在原来的类中添加一些代码,修复bug。
<?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_INT;
// 主键是否自增
protected $_autoinc = true;
// Mongo默认关闭字段检测 可以动态追加字段
protected $autoCheckFields = false;
// 链操作方法列表
protected $methods = array('table','order','auto','filter','validate');
//数据库配置
protected $connection = 'DB_MONGO';
/**
* 利用__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);
}
/**
* 获取下一ID 用于自动增长型
* @access public
* @param string $pk 字段名 默认为主键
* @return mixed
*/
public function getMongoNextId($pk=''){
if(empty($pk)) {
$pk = $this->getPk();
}
return $this->db->mongo_next_id($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->mongo_next_id($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);
//解决参数$field 指定的字段顺序与数据库中记录字段顺序不一致时导致的返回结果$key不正确问题
//2015-08-15 by bing
if(!(array_search($_field[0], array_keys($resultSet[0]))===false)){
$key=$_field[0];
$key2=$_field[1];
}
$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)) {
foreach ($result as $val){
if(strpos($field,'.')){
$tmp=explode('.', $field);
foreach ($tmp as $t){
$val=$val[$t];
}
$array[] = $val;
}else{
$array[] = $val[$field];
}
}
return 1 == $options['limit'] ? $array[0] : $array;
}
}
return null;
}
/**
* 执行Mongo指令
* @access public
* @param array $command 指令
* @return mixed
*/
public function command($command) {
return $this->db->command($command);
}
/**
* 执行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
//bing 2015-8-15 获取数据表
if(empty($this->dbName) && !empty($this->connection)){
$db_config=C($this->connection);
$this->dbName=$db_config['DB_NAME'];
}
$this->db->switchCollection($this->getTableName(),$this->dbName);
}
/**
* 得到完整的数据表名 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;
}
}
上面是一个修改之后的完整的mongo类。
其中
(1)
<span style="color:#FF0000;">protected $connection = 'DB_MONGO'; 是链接配置config对应的DB_mongo
(2)
</span>
//解决参数$field 指定的字段顺序与数据库中记录字段顺序不一致时导致的返回结果$key不正确问题
//2015-08-15 by bing
if(!(array_search($_field[0], array_keys($resultSet[0]))===false)){
$key=$_field[0];
$key2=$_field[1];
}
这一块是添加进去的,建立数据库的缓存
(3)
//bing 2015-8-15 获取数据表
if(empty($this->dbName) && !empty($this->connection)){
$db_config=C($this->connection);
$this->dbName=$db_config['DB_NAME'];
}
$this->db->switchCollection($this->getTableName(),$this->dbName);
这一个函数在tp原来的MongoModel里只会默认链接到MYSQL的配置dbname中去,现要作修改,如上所示。
二、在tp中配置DB_MONGO
(1)在config文件 中添加
'DB_MONGO'=>
array(
'DB_TYPE' => 'mongo', // 数据库类型
'DB_HOST' => 'localhost', // 服务器地址
'DB_NAME' => 'test', // 数据库名
'DB_USER' => '', // 用户名
'DB_PWD' => '', // 密码
'DB_PORT' => '27017', // 端口
),
mongo默认是没有认证的,所以用户名和密码为空。
(2)在MongoModel的添加
protected $connection = 'DB_MONGO'; 是链接配置config对应的DB_mongo,就是上面的完整的Mongo类。
至此数据库的配置已经完成。
三、安装php_mongo驱动
在Windows上安装驱动必须要和自己所用的php大版本想对应。
(1)下载对应版本的php_mongo。
(2)将php_mongo.dll复制到php的ext文件中
(3)在php.ini中添加extension=php_mongo.dll
(4)重启Apache
(5)访问phpinfo,搜索mongo
出现上面代表配置成功。
四、tp上mongo的CURD操作
(1)php连接mongo,创建实例化对象
$m=new \Think\Model\MongoModel('user');
其中“user”是集合,相当于mysql中的表。
(2)添加——add()
查询和mysql基本一样,
example:
$data=array(
"name"=>"李四",
"addr"=>"深圳",
"sex"=>"男",
"info"=>array(
"age"=>20,
"phone"=>"12345",
),
);
$result=$m->add($data);
这样就将数据添加作为一个文档,相当于mysql的一条记录。
(2)查询——select(),getField()
如果要查询刚才插进去的$data
$map['name']="李四",
$result=$m->where($map)->select();
查找的是整个文档,返回的是一个数组。
如图
array(5) {
["_id"] => int(4)
["name"] => string(6) "李四"
["addr"] => string(6) "深圳"
["sex"] => string(3) "男"
["info"] => array(2) {
["age"] => int(20)
["phone"] => string(5) "12345"
}
}
如果要查一个字段,可以用getField();
$result=$m->where($map)->getField('info');
查出来如图
array(2) {
["age"] => int(20)
["phone"] => string(5) "12345"
}
如果要查age这个键的值,直接
$result=$m->where($map)->getField('info.age');
返回int(20)
时间区段的查询,要用数组形式查询
比如文档中有一个键“time”,要查找2015/815-2015/9/1之间的文档。
$map=array('time'=>array('$gt'=>int,'$lt'=>int))
$result=$m->where($map)->select()
其中int代表时间戳。
(3)删除——delete();
$result=$m->where($map)->delete();
这个delete的删除是删除整个文档。
(4)修改——save()
set
例如重新修改文档中的sex=>‘男’,改为sex=>'"女"
$update['sex']=array('set','女');
$m->where($map)->save($update);
结果:
array(5) {
["_id"] => int(4)
["name"] => string(6) "李四"
["addr"] => string(6) "深圳"
["sex"] => string(3) "女"
["info"] => array(2) {
["age"] => int(20)
["phone"] => string(5) "12345"
}
例如修改内嵌文档中的某个键的值,比如age
$update['info.age']=array('set',30);
$m->where($map)->save($update);
结果:
array(5) {
["_id"] => int(3)
["name"] => string(6) "李四"
["addr"] => string(6) "深圳"
["sex"] => string(3) "女"
["info"] => array(2) {
["age"] => int(30)
["phone"] => string(5) "12345"
}
}
unset
比如要删除某个键。
例如删除文档中的addr键
$update['addr']=array('unset');
$m->where($map)->save($update);
结果:
array(4) {
["_id"] => int(3)
["name"] => string(6) "李四"
["sex"] => string(3) "女"
["info"] => array(2) {
["age"] => int(30)
["phone"] => string(5) "12345"
}
}
可以看到已经没有addr键了
push——追加一个值到字段(必须是数组类型)里面去
比如一个数组(这里不包括关系型数组)
$data=array(
"name"=>"李四",
"addr"=>"深圳",
"sex"=>"男",
"info"=>array(
"age"=>20,
"phone"=>"12345",
),
"num"=>array();
);
$result=$m->add($data);
num是一个数组,向里面修改添加数据。
$update['num']=array('push',1);
$m->where($map)->save($update);
结果:
array(5) {
["_id"] => int(3)
["name"] => string(6) "李四"
["sex"] => string(3) "女"
["info"] => array(2) {
["age"] => int(30)
["phone"] => string(5) "12345"
}
["num"] => array(1) {
[0] => int(1)
}
}
如果要添加数组
$update['num']=array('push',array('subject'=>'php'));
$m->where($map)->save($update);
结果:
array(5) {
["_id"] => int(3)
["name"] => string(6) "李四"
["sex"] => string(3) "女"
["info"] => array(2) {
["age"] => int(30)
["phone"] => string(5) "12345"
}
["num"] => array(2) {
[0] => int(1)
[1] => array(1) {
["subject"] => string(3) "php"
}
}
}
pull——根据值删除字段(必须是数组字段)中的一个值
现在要删除刚才添加在num数组里的array('suject'=>'php')
$update['num']=array('pull',array('subject'=>'php'));
$m->where($map)->save($update);
结果:
array(5) {
["_id"] => int(3)
["name"] => string(6) "李四"
["sex"] => string(3) "女"
["info"] => array(2) {
["age"] => int(30)
["phone"] => string(5) "12345"
}
["num"] => array(1) {
[0] => int(1)
}
}
可以看到num数组里已经没有 array('suject'=>'php');
(5)统计——count()
$result=$m->where($map)->count();
至此,以上方法都是经过实践测试的,mongo最常用的,能满足业务要求的方法总结结束。如有什么不对的地方,请指出!