一.序言
使用PHP+MongoDB的企業級使用者很多,因為MongoDB對非結構化資料的儲存很方便。在PHP5及以前,官方提供了兩個擴充,Mongo和MongoDB,其中Mongo是對以MongoClient等幾個核心類為基礎的類群進行操作,封裝得很方便,所以基本上都會選擇Mongo擴充,詳情請見官方手冊:
http://php.net/manual/en/class.mongoclient.php
1
但是隨著PHP5升級到PHP7,官方不再支援Mongo擴充,只支援MongoDB,而PHP7的效能提升巨大,讓人無法割捨,所以怎麼把Mongo替換成MongoDB成為了一個亟待解決的問題。MongoDB引入了命名空間,但是功能封裝非常差,如果非要用原生的擴充,幾乎意味著寫原生的Mongo語句。這種想法很違背ORM簡化dbIO操作帶來的文法問題而專註邏輯最佳化的思路。詳情也可參見官方手冊;
http://php.net/manual/en/class.mongodb-driver-manager.php
1
在這種情況之下,MongoDB官方忍不住了,為了方便使用,增加市場佔有率,推出了基於MongoDB擴充的庫,詳情參見:
https://github.com/mongodb/mongo-php-library
1
實際上在我們使用的過程中,總是希望能夠實現儘可能的解耦,於是分層清晰變得尤為重要。由於官方的庫並不能實現筆者分離和特定的功能需要,於是筆者自己造了一次輪子。
二.自我封裝的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(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
以上為單例模式的構造方法,顯然定義了兩個沒有意義的擷取參數的函數,實際上初始化的入口應該在繼承的子類中完成重寫。每一個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); }
1 2 3 4 5 6 7 8 9
這裡為簡單地直接初始化。而建構函式的設計決定了兩者並不衝突,至於為何要設計兩種初始化方法,是出於對清晰分層的更好支援的原因
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; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
在處理的時候用到了棧的思想,在PHP中使用數組進行代替,實際上,PHP的數組函數還是相當契合棧的思路的,比如