虚位以待(AD)
虚位以待(AD)
首页 > 数据库 > MongoDB数据库 > 从PHP5到PHP7自我封装MongoDB以及平滑升级

从PHP5到PHP7自我封装MongoDB以及平滑升级
类别:MongoDB数据库   作者:码皇   来源:<a href="http://blog.csdn.net/byr_wy" target="_blank" rel="nofollow"   点击:

从PHP5到PHP7自我封装MongoDB以及平滑升级,使用PHP+MongoDB的企业级用户很多,因为MongoDB对非结构化数据的存储很方便。在PHP5及以前,官方提供了两个扩展,Mongo和MongoDB。

一.序言

从PHP5到PHP7自我封装MongoDB以及平滑升级,使用PHP+MongoDB的企业级用户很多,因为MongoDB对非结构化数据的存储很方便。在PHP5及以前,官方提供了两个扩展,Mongo和MongoDB,其中Mongo是对以MongoClient等几个核心类为基础的类群进行操作,封装得很方便,所以基本上都会选择Mongo扩展,详情请见官方手册:

    http://php.net/manual/en/class.mongoclient.php

但是随着PHP5升级到PHP7,官方不再支持Mongo扩展,只支持MongoDB,而PHP7的性能提升巨大,让人无法割舍,所以怎么把Mongo替换成MongoDB成为了一个亟待解决的问题。MongoDB引入了命名空间,但是功能封装非常差,如果非要用原生的扩展,几乎意味着写原生的Mongo语句。这种想法很违背ORM简化dbIO操作带来的语法问题而专注逻辑优化的思路。详情也可参见官方手册;

    http://php.net/manual/en/class.mongodb-driver-manager.php

在这种情况之下,MongoDB官方忍不住了,为了方便使用,增加市场占有率,推出了基于MongoDB扩展的库,详情参见:

    https://github.com/mongodb/mongo-php-library

实际上在我们使用的过程中,总是希望能够实现尽可能的解耦,于是分层清晰变得尤为重要。由于官方的库并不能实现笔者分离和特定的功能需要,于是笔者自己造了一次轮子。

二.自我封装的MongoDBClient类

1.构造函数
笔者希望构造函数能够有两种方式,一种以单例模式去实现对Model层继承传参构造,另一种是简单地直接构造,以实现代码的充分复用和封装类的广泛适用。

    public $_client;
    public $_manager;
    public $_db;
    public $_collection;
    public function __construct(){
    $config=$this->getDbConnection();
    if(!empty($config['
    server'
    ]) && !empty($config['
    db'
    ])){
    $uri=$config['
    server'
    ]."/".$config['
    db'
    ];
    if(isset($config['
    urioptions'
    ])){
    $urioptions=$config['
    urioptions'
    ];
    }
    else{
    $urioptions=array();
    }
    if(isset($config['
    driveroptions'
    ])){
    $driveroptions=$config['
    driveroptions'
    ];
    }
    else{
    $driveroptions=array();
    }
    $this->setClient($uri,$urioptions,$driveroptions);
    $this->setDatabase($config['
    db'
    ]);
    }
    $collectionName=$this->collectionName();
    if($this->getDatabase()){
    $this->setCollection($collectionName);
    }
    }
    public function collectionName(){
    return '
    '
    ;
    }
    public function getDbConnection(){
    return array();
    }

以上为单例模式的构造方法,显然定义了两个没有意义的获取参数的函数,实际上初始化的入口应该在继承的子类中完成重写。每一个set函数,都会把实例传给该对象的属性以保存并在后续中调用。

    public function initInstance($uri,$db,$collectionName) {
    // $config = $this->getMongoConfig();
    // $tempStr='
    mongodb://'
    .$config['
    username'
    ].'
    :'
    .$config['
    password'
    ].'
    @'
    .$config['
    host'
    ].'
    /'
    .$config['
    db'
    ];
    // $mongodbclient=new MongoDBClient();
    $this->setClient($uri);
    $this->setDatabase($db);
    $this->setCollection($collectionName);
    }

这里为简单地直接初始化。而构造函数的设计决定了两者并不冲突,至于为何要设计两种初始化方法,是出于对清晰分层的更好支持的原因

2.filter过滤器的构造
从Mongodb官方的原生到php官方的扩展到Mongodb的依赖库,对于filter的构建方法非常粗暴,就是让人去直接写Mongodb的filter的原生语句,这与ORM简化语法耦合的思路大相径庭。为了方便别人使用复杂的过滤器,笔者对过滤器进行了简化的构造。具体思路是把一个语义化的过滤器看作一个算式,一组条件看作一个数,连接符则当作运算符,通过中缀表达式转后缀表达式实现去括号,然后再执行后缀表达式,实现语义化的连接符的语法化,从而简化了业务层的开发者的成本。详情如下:

    public function filterConstructor($key,$operator,$value,$connector=array()){
    $filter=array();
    $subfilter=array();
    switch ($operator) {
    case '
    ='
    : $subfilter=array($key=>$value);
    break;
    case '
    >'
    : $subfilter=array($key=>array('
    $gt'
    =>$value));
    break;
    case '
    >='
    : $subfilter=array($key=>array('
    $gte'
    =>$value));
    break;
    case '
    <'
    : $subfilter=array($key=>array('
    $lt'
    =>$value));
    break;
    case '
    <='
    : $subfilter=array($key=>array('
    $lte'
    =>$value));
    break;
    case '
    !='
    : $subfilter=array($key=>array('
    $ne'
    =>$value));
    break;
    default: die();
    break;
    }
    $filter=array_merge($filter,$subfilter);
    return $filter;
    }
    /* * construct a easy-and filter with double arrays via key-value input * @param (Array)$trible1 (Array)$trible2 * @return an array of mongo-dialect filter * @author wangyang */ public function andFilterConstructor($trible1,$trible2){
    $ret1=$this->filterConstructor($trible1[0],$trible1[1],$trible1[2]);
    $ret2=$this->filterConstructor($trible2[0],$trible2[1],$trible2[2]);
    array_merge($ret1,$ret2);
    return $ret1;
    }
    /* * construct a easy-or filter with double arrays via key-value input * @param (Array)$trible1 (Array)$trible2 * @return an array of mongo-dialect filter * @author wangyang */ public function orFilterConstructor($trible1,$trible2){
    $ret1=$this->filterConstructor($trible1[0],$trible1[1],$trible1[2]);
    $ret2=$this->filterConstructor($trible2[0],$trible2[1],$trible2[2]);
    $ret=array('
    $or'
    =>array());
    array_push($ret['
    $or'
    ],$ret1);
    array_push($ret['
    $or'
    ],$ret2);
    return $ret;
    }
    /* * construct a easy-and filter with double filters * @param (Array)$query1 (Array)$query2 * @return an array of mongo-dialect filter * @author wangyang */ public function onlyAndFilterConstructor($query1,$query2){
    $query1=array_merge_recursive($query1,$query2);
    return $query1;
    }
    /* * construct a easy-or filter with double filters * @param (Array)$query1 (Array)$query2 * @return an array of mongo-dialect filter * @author wangyang */ public function onlyOrFilterConstructor($query1,$query2){
    $query=array('
    $or'
    =>array());
    array_push($query['
    $or'
    ],$query1);
    array_push($query['
    $or'
    ],$query2);
    return $query;
    }
    /* * resolve the complicated connectors set filter * @param (Array)$query e.g. array(filterarray1(),$connector,filterarray2()) * e.g. array(arr1(),'
    or'
    ,'
    ('
    ,arr2(),'
    and'
    ,arr3(),'
    )'
    ) * @return an array of mongo-dialect filter * @author wangyang */ public function queryFilterConstructor($query){
    $priority=array('
    ('
    =>3,'
    and'
    =>2,'
    or'
    =>2,'
    )'
    =>1);
    $stack1=array();
    $stack2=array();
    //transfer nifix expression to postfix expression foreach ($query as $key => $value) {
    if(is_array($value)){
    array_push($stack2,$value);
    }
    elseif($value=='
    ('
    ||empty($stack1)){
    array_push($stack1,$value);
    }
    elseif($value=='
    )'
    ) {
    while(($top=array_pop($stack1))!=='
    ('
    ){
    array_push($stack2,$top);
    }
    }
    elseif(end($stack1)=='
    ('
    ){
    array_push($stack1,$value);
    }
    else{
    while($priority[$value]<$priority[end($stack1)]){
    $top=array_pop($stack1);
    array_push($stack2,$top);
    }
    array_push($stack1,$value);
    }
    }
    while(!empty($stack1)){
    $top=array_pop($stack1);
    array_push($stack2,$top);
    }
    foreach ($stack2 as $key => $value) {
    if(is_array($value)){
    $stack2[$key]=$this->filterConstructor($value[0],$value[1],$value[2]);
    }
    }
    //compute the postfix expression foreach ($stack2 as $key => $value) {
    if(is_array($value)){
    array_push($stack1,$value);
    }
    else{
    $top=array_pop($stack1);
    $subtop=array_pop($stack1);
    if($value=='
    and'
    ){
    $ret=$this->onlyAndFilterConstructor($top,$subtop);
    array_push($stack1,$ret);
    }
    elseif($value=='
    or'
    ){
    $ret=$this->onlyOrFilterConstructor($top,$subtop);
    array_push($stack1,$ret);
    }
    else{
    die('
    undefined connector'
    );
    }
    }
    }
    $ret=array_pop($stack1);
    return $ret;
    }

在处理的时候用到了栈的思想,在PHP中使用数组进行代替,实际上,PHP的数组函数还是相当契合栈的思路的,比如插入array_push(),删除顶部元素array_pop(),而在转逆波兰式的过程中,完成对最基础的语句的拼装,后面的复杂语句通过迭代来实现。
比如要实现{“likes”: {$gt:50}, $or: [{“by”: “菜鸟教程”},{“title”: “MongoDB 教程”}]}这样一句查询过滤器,我们就可以用 [[‘likes’,’>’,’50’],’or’,’(‘,[‘by’,’=’,’菜鸟教程’],’and’,[‘title’,’=’,’MongoDB教程’],’)’]来代替了。这大概是我在这个类里最得意的部分了。

3.从数据库到聚合到具体文档的CURD
这个就直接上demo吧~
值得注意的是官方的find()返回为一个cursor,通过foreach遍历输出的结果却是一组documents,说明其实官方的对象设计得很不友好

    require '
    MongodbExtension.php'
    ;
    $mongo=new MongoDBClient();
    $mongo->setClient("mongodb://127.0.0.1:27017");
    function DataBase($mongo){
    //列出所有数据库名 $databases=$mongo->listDatabases();
    //创建数据库,获得数据库实例 $database=$mongo->createDatabase('
    BUPT'
    );
    //删除数据库 $mongo->dropDatabase('
    BUPT'
    );
    //选择数据库,获得数据库实例 $database=$mongo->selectDatabase('
    wangyang'
    );
    }
    function Collection($mongo){
    //列出所有集合 $collections=$mongo->listCollections();
    //创建集合,获得集合实例 $collection=$mongo->createCollection('
    BUPT'
    );
    //删除集合 $mongo->dropCollection('
    BUPT'
    );
    //选择集合,获得集合实例 $collection=$mongo->selectCollection('
    test'
    );
    }
    function DocumentInsert($mongo){
    //插入一条数据 $insert=array('
    name'
    =>'
    BUPT'
    );
    $mongo->collectionInsertOne($insert);
    //插入多条数据 $inserts=array(array('
    name'
    =>'
    BUPT'
    ),array('
    by'
    =>'
    wangyang'
    ));
    $mongo->collectionInsertMany($inserts);
    }
    function DocumentDelete($mongo){
    //简单的过滤器设置 $filter=$mongo->filterConstructor('
    name'
    ,'
    ='
    ,'
    BUPT'
    );
    //复杂的过滤器 $filter=$mongo->queryFilterConstructor(array(array('
    by'
    ,'
    ='
    ,'
    me'
    ),'
    or'
    ,array('
    title'
    ,'
    ='
    ,'
    BUPT'
    )));
    //删除很多条,返回删了多少条 $deletenum=$mongo->collectionDeleteMany($filter);
    //删除一条 $mongo->collectionDeleteOne($filter);
    }
    function DocumentUpdate($mongo){
    //简单的过滤器设置 $filter=$mongo->filterConstructor('
    name'
    ,'
    ='
    ,'
    BUPT'
    );
    //复杂的过滤器 $filter=$mongo->queryFilterConstructor(array(array('
    by'
    ,'
    ='
    ,'
    me'
    ),'
    or'
    ,array('
    title'
    ,'
    ='
    ,'
    BUPT'
    )));
    //更新后的键值对 $update=$mongo->updateConstructor('
    title'
    ,'
    THU'
    );
    //更新一条 $mongo->collectionUpdateOne($filter,$update);
    //更新很多条 $mongo->collectionUpdateMany($filter,$update);
    }
    function DocumentFind($mongo){
    //简单的过滤器设置 $filter=$mongo->filterConstructor('
    name'
    ,'
    ='
    ,'
    BUPT'
    );
    //复杂的过滤器 $filter=$mongo->queryFilterConstructor(array(array('
    by'
    ,'
    ='
    ,'
    me'
    ),'
    or'
    ,array('
    title'
    ,'
    ='
    ,'
    BUPT'
    )));
    //选项,目前只提供limit和sort设置,可为空 $option=$mongo->optionConstructor(4,array('
    key'
    =>'
    _id'
    ,'
    value'
    =>'
    -1'
    ));
    //查找一条,返回一条数据实例 $document=$mongo->collectionFindOne($filter,$option);
    //查找许多条,返回数据实例数组 $documents=$mongo->collectionFindMany($filter,$option);
    }

4.异常处理
全程try catch,进入一个异常处理函数,详情如下

    public function throwException($e){
    if($e instanceof UnsupportedException){
    die("options are used and not supported by the selected server (e.g. collation, readConcern, writeConcern).");
    }
    elseif($e instanceof InvalidArgumentException){
    die("errors related to the parsing of parameters or options.");
    }
    elseif($e instanceof MongoDBDriverExceptionRuntimeException){
    die("other errors at the driver level (e.g. connection errors).");
    }
    elseif($e instanceof UnexpectedValueException){
    die(" the command response from the server was malformed.");
    }
    elseif($e instanceof MongoDBDriverExceptionBulkWriteException ){
    die("errors related to the write operation. Users should inspect the value returned by getWriteResult() to determine the nature of the error.");
    }
    }

三.平滑过度

话说的是平滑过度,但实际上并不可能。举例而论,Mongo和MongoDB很多相同功能的函数的返回值都不一样,设计上并不统一。其实这很能反应出老代码的设计模式有没有问题,如果分层清晰,那么在逻辑层里就根本不需要改动,需要改动的只是Model层到扩展层的关联层,但是很遗憾的是往往事与愿违,笔者花费了很多时间在业务代码上来做逻辑改动,只是为了适应新的版本。但是说到底,感觉自己单独设计后台逻辑时,也不会考虑这么多,毕竟谁能想到官方都这么坑呢?想得太多也是给自己挖坑,可能到了过度设计的范畴了。

需要注意的是Mongo的时间类MongoDate已经不再适用,而MongoDB的UTCDateTime的格式并不是简单的unix时间戳,而是以微秒为单位的时间戳,升级的时候需要注意这一点,这意味新的时间戳是13位,而旧的时间戳是10位,而且在获取时间戳的方式上也大不相同,MongoDate中设置了Get/set魔术方法,可以直接获取时间戳属性,而在UTCDateTime中则根本没有public的成员,只能通过调用内部函数获得时间戳。

此外,Mongo中的返回值是array结构的,MongoDB的返回值则是object结构的,需要通过BSONDocument类的getArrayCopy()方法进行转换,笔者通过(array)强制转换也是Ok的。

建议对这些部分也进行一个封装,如下

    public static function getUTCDateTime($timestamp=NULL){
    return new MongoDBBSONUTCDateTime($timestamp);
    }

这样通过静态方法可以不实例化直接调用,使用起来很方便。

相关热词搜索: