EMongoCriteria.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. <?php
  2. /**
  3. * EMongoCriteria.php
  4. *
  5. * PHP version 5.2+
  6. *
  7. * @author Dariusz Górecki <darek.krk@gmail.com>
  8. * @author Invenzzia Group, open-source division of CleverIT company http://www.invenzzia.org
  9. * @copyright 2011 CleverIT http://www.cleverit.com.pl
  10. * @license http://www.yiiframework.com/license/ BSD license
  11. * @version 1.3
  12. * @category ext
  13. * @package ext.YiiMongoDbSuite
  14. *
  15. */
  16. /**
  17. * EMongoCriteria class
  18. *
  19. * This class is a helper for building MongoDB query arrays, it support three syntaxes for adding conditions:
  20. *
  21. * 1. 'equals' syntax:
  22. * $criteriaObject->fieldName = $value; // this will produce fieldName == value query
  23. * 2. fieldName call syntax
  24. * $criteriaObject->fieldName($operator, $value); // this will produce fieldName <operator> value
  25. * 3. addCond method
  26. * $criteriaObject->addCond($fieldName, $operator, $vale); // this will produce fieldName <operator> value
  27. *
  28. * For operators list {@see EMongoCriteria::$operators}
  29. *
  30. * @author Dariusz Górecki <darek.krk@gmail.com>
  31. * @since v1.0
  32. */
  33. class EMongoCriteria extends CComponent
  34. {
  35. /**
  36. * @since v1.0
  37. * @var array $operators supported operators lists
  38. */
  39. public static $operators = array(
  40. 'greater' => '$gt',
  41. '>' => '$gt',
  42. 'greatereq' => '$gte',
  43. '>=' => '$gte',
  44. 'less' => '$lt',
  45. '<' => '$lt',
  46. 'lesseq' => '$lte',
  47. '<=' => '$lte',
  48. 'noteq' => '$ne',
  49. '!=' => '$ne',
  50. '<>' => '$ne',
  51. 'in' => '$in',
  52. 'notin' => '$nin',
  53. 'and' => '$and',
  54. 'all' => '$all',
  55. 'size' => '$size',
  56. 'type' => '$type',
  57. 'exists' => '$exists',
  58. 'notexists' => '$exists',
  59. 'elemmatch' => '$elemMatch',
  60. 'mod' => '$mod',
  61. '%' => '$mod',
  62. 'equals' => '$$eq',
  63. 'eq' => '$$eq',
  64. '==' => '$$eq',
  65. 'where' => '$where',
  66. 'or' => '$or'
  67. );
  68. const SORT_ASC = 1;
  69. const SORT_DESC = -1;
  70. private $_select = array();
  71. private $_limit = null;
  72. private $_offset = null;
  73. private $_conditions = array();
  74. private $_sort = array();
  75. private $_workingFields = array();
  76. private $_useCursor = null;
  77. /**
  78. * Constructor
  79. * Example criteria:
  80. *
  81. * <PRE>
  82. * 'criteria' = array(
  83. * 'conditions'=>array(
  84. * 'fieldName1'=>array('greater' => 0),
  85. * 'fieldName2'=>array('>=' => 10),
  86. * 'fieldName3'=>array('<' => 10),
  87. * 'fieldName4'=>array('lessEq' => 10),
  88. * 'fieldName5'=>array('notEq' => 10),
  89. * 'fieldName6'=>array('in' => array(10, 9)),
  90. * 'fieldName7'=>array('notIn' => array(10, 9)),
  91. * 'fieldName8'=>array('all' => array(10, 9)),
  92. * 'fieldName9'=>array('size' => 10),
  93. * 'fieldName10'=>array('exists'),
  94. * 'fieldName11'=>array('notExists'),
  95. * 'fieldName12'=>array('mod' => array(10, 9)),
  96. * 'fieldName13'=>array('==' => 1)
  97. * ),
  98. * 'select'=>array('fieldName', 'fieldName2'),
  99. * 'limit'=>10,
  100. * 'offset'=>20,
  101. * 'sort'=>array('fieldName1'=>EMongoCriteria::SORT_ASC, 'fieldName2'=>EMongoCriteria::SORT_DESC),
  102. * );
  103. * </PRE>
  104. * @param mixed $criteria
  105. * @since v1.0
  106. */
  107. public function __construct($criteria=null)
  108. {
  109. if(is_array($criteria))
  110. {
  111. if(isset($criteria['conditions']))
  112. foreach($criteria['conditions'] as $fieldName=>$conditions)
  113. {
  114. $fieldNameArray = explode('.', $fieldName);
  115. if(count($fieldNameArray) === 1)
  116. $fieldName = array_shift($fieldNameArray);
  117. else
  118. $fieldName = array_pop($fieldNameArray);
  119. foreach($conditions as $operator => $value)
  120. {
  121. $this->setWorkingFields($fieldNameArray);
  122. $operator = strtolower($operator);
  123. $this->$fieldName($operator, $value);
  124. }
  125. }
  126. if(isset($criteria['select']))
  127. $this->select($criteria['select']);
  128. if(isset($criteria['limit']))
  129. $this->limit($criteria['limit']);
  130. if(isset($criteria['offset']))
  131. $this->offset($criteria['offset']);
  132. if(isset($criteria['sort']))
  133. $this->setSort($criteria['sort']);
  134. if(isset($criteria['useCursor']))
  135. $this->setUseCursor($criteria['useCursor']);
  136. }
  137. else if($criteria instanceof EMongoCriteria)
  138. $this->mergeWith($criteria);
  139. }
  140. /**
  141. * Merge with other criteria
  142. * - Field list operators will be merged
  143. * - Limit and offet will be overriden
  144. * - Select fields list will be merged
  145. * - Sort fields list will be merged
  146. * @param array|EMongoCriteria $criteria
  147. * @since v1.0
  148. */
  149. public function mergeWith($criteria)
  150. {
  151. if(is_array($criteria))
  152. $criteria = new EMongoCriteria($criteria);
  153. else if(empty($criteria))
  154. return $this;
  155. $opTable = array_values(self::$operators);
  156. foreach($criteria->_conditions as $fieldName=>$conds)
  157. {
  158. if(
  159. is_array($conds) &&
  160. count(array_diff(array_keys($conds), $opTable)) == 0
  161. )
  162. {
  163. if(isset($this->_conditions[$fieldName]) && is_array($this->_conditions[$fieldName]))
  164. {
  165. foreach($this->_conditions[$fieldName] as $operator => $value)
  166. if(!in_array($operator, $opTable))
  167. unset($this->_conditions[$fieldName][$operator]);
  168. }
  169. else
  170. $this->_conditions[$fieldName] = array();
  171. foreach($conds as $operator => $value)
  172. $this->_conditions[$fieldName][$operator] = $value;
  173. }
  174. else
  175. $this->_conditions[$fieldName] = $conds;
  176. }
  177. if(!empty($criteria->_limit))
  178. $this->_limit = $criteria->_limit;
  179. if(!empty($criteria->_offset))
  180. $this->_offset = $criteria->_offset;
  181. if(!empty($criteria->_sort))
  182. $this->_sort = array_merge($this->_sort, $criteria->_sort);
  183. if(!empty($criteria->_select))
  184. $this->_select = array_merge($this->_select, $criteria->_select);
  185. return $this;
  186. }
  187. /**
  188. * If we have operator add it otherwise call parent implementation
  189. * @see CComponent::__call()
  190. * @since v1.0
  191. */
  192. public function __call($fieldName, $parameters)
  193. {
  194. if(isset($parameters[0]))
  195. $operatorName = strtolower($parameters[0]);
  196. if(isset($parameters[1]) || ($parameters[1] === null))
  197. $value = $parameters[1];
  198. if(is_numeric($operatorName))
  199. {
  200. $operatorName = strtolower(trim($value));
  201. $value = (strtolower(trim($value)) === 'exists') ? true : false;
  202. }
  203. if(in_array($operatorName, array_keys(self::$operators)))
  204. {
  205. array_push($this->_workingFields, $fieldName);
  206. $fieldName = implode('.', $this->_workingFields);
  207. $this->_workingFields = array();
  208. switch($operatorName)
  209. {
  210. case 'exists':
  211. $this->addCond($fieldName, $operatorName, true);
  212. break;
  213. case 'notexists':
  214. $this->addCond($fieldName, $operatorName, false);
  215. break;
  216. default:
  217. $this->addCond($fieldName, $operatorName, $value);
  218. }
  219. return $this;
  220. }
  221. else
  222. return parent::__call($fieldName, $parameters);
  223. }
  224. /**
  225. * @since v1.0.2
  226. */
  227. public function __get($name)
  228. {
  229. array_push($this->_workingFields, $name);
  230. return $this;
  231. }
  232. /**
  233. * @since v1.0.2
  234. */
  235. public function __set($name, $value)
  236. {
  237. array_push($this->_workingFields, $name);
  238. $fieldList = implode('.', $this->_workingFields);
  239. $this->_workingFields = array();
  240. $this->addCond($fieldList, '==', $value);
  241. }
  242. /**
  243. * Return query array
  244. * @return array query array
  245. * @since v1.0
  246. */
  247. public function getConditions()
  248. {
  249. return $this->_conditions;
  250. }
  251. /**
  252. * @since v1.0
  253. */
  254. public function setConditions(array $conditions)
  255. {
  256. $this->_conditions = $conditions;
  257. }
  258. /**
  259. * @since v1.0
  260. */
  261. public function getLimit()
  262. {
  263. return $this->_limit;
  264. }
  265. /**
  266. * @since v1.0
  267. */
  268. public function setLimit($limit)
  269. {
  270. $this->limit($limit);
  271. }
  272. /**
  273. * @since v1.0
  274. */
  275. public function getOffset()
  276. {
  277. return $this->_offset;
  278. }
  279. /**
  280. * @since v1.0
  281. */
  282. public function setOffset($offset)
  283. {
  284. $this->offset($offset);
  285. }
  286. /**
  287. * @since v1.0
  288. */
  289. public function getSort()
  290. {
  291. return $this->_sort;
  292. }
  293. /**
  294. * @since v1.0
  295. */
  296. public function setSort(array $sort)
  297. {
  298. $this->_sort = $sort;
  299. }
  300. /**
  301. * @since v1.3.7
  302. */
  303. public function getUseCursor()
  304. {
  305. return $this->_useCursor;
  306. }
  307. /**
  308. * @since v1.3.7
  309. */
  310. public function setUseCursor($useCursor)
  311. {
  312. $this->_useCursor = $useCursor;
  313. }
  314. /**
  315. * Return selected fields
  316. *
  317. * @param boolean $forCursor MongoCursor::fields() method requires
  318. * the fields to be specified as a hashmap. When this
  319. * parameter is set to true, then we'll return
  320. * the fields in this format
  321. * @since v1.3.1
  322. */
  323. public function getSelect($forCursor = false)
  324. {
  325. if (!$forCursor) return $this->_select;
  326. return array_fill_keys($this->_select, true); // PHP 5.2.0+ required!
  327. }
  328. /**
  329. * @since v1.3.1
  330. */
  331. public function setSelect(array $select)
  332. {
  333. $this->_select = $select;
  334. }
  335. /**
  336. * @since v1.3.1
  337. */
  338. public function getWorkingFields()
  339. {
  340. return $this->_workingFields;
  341. }
  342. /**
  343. * @since v1.3.1
  344. */
  345. public function setWorkingFields(array $select)
  346. {
  347. $this->_workingFields = $select;
  348. }
  349. /**
  350. * List of fields to get from DB
  351. * Multiple calls to this method will merge all given fields
  352. *
  353. * @param array $fieldList list of fields to select
  354. * @since v1.0
  355. */
  356. public function select(array $fieldList=null)
  357. {
  358. if($fieldList!==null)
  359. $this->_select = array_merge($this->_select, $fieldList);
  360. return $this;
  361. }
  362. /**
  363. * Set linit
  364. * Multiple calls will overrride previous value of limit
  365. *
  366. * @param integer $limit limit
  367. * @since v1.0
  368. */
  369. public function limit($limit)
  370. {
  371. $this->_limit = intval($limit);
  372. return $this;
  373. }
  374. /**
  375. * Set offset
  376. * Multiple calls will override previous value
  377. *
  378. * @param integer $offset offset
  379. * @since v1.0
  380. */
  381. public function offset($offset)
  382. {
  383. $this->_offset = intval($offset);
  384. return $this;
  385. }
  386. /**
  387. * Add sorting, avaliabe orders are: EMongoCriteria::SORT_ASC and EMongoCriteria::SORT_DESC
  388. * Each call will be groupped with previous calls
  389. * @param string $fieldName
  390. * @param integer $order
  391. * @since v1.0
  392. */
  393. public function sort($fieldName, $order)
  394. {
  395. $this->_sort[$fieldName] = intval($order);
  396. return $this;
  397. }
  398. /**
  399. * Add condition
  400. * If specified field already has a condition, values will be merged
  401. * duplicates will be overriden by new values!
  402. * @param string $fieldName
  403. * @param string $op operator
  404. * @param mixed $value
  405. * @since v1.0
  406. */
  407. public function addCond($fieldName, $op, $value)
  408. {
  409. $op = self::$operators[$op];
  410. if($op == self::$operators['or'])
  411. {
  412. if(!isset($this->_conditions[$op]))
  413. {
  414. $this->_conditions[$op] = array();
  415. }
  416. $this->_conditions[$op][] = array($fieldName=>$value);
  417. } else if ($op == self::$operators['where']){
  418. $this->_conditions[$op] = $value;
  419. } else if ($op == self::$operators['and']){
  420. if(!isset($this->_conditions[$op]))
  421. {
  422. $this->_conditions[$op] = array();
  423. }
  424. $this->_conditions[$op][] = $value;
  425. } else {
  426. if(!isset($this->_conditions[$fieldName]) && $op != self::$operators['equals'])
  427. $this->_conditions[$fieldName] = array();
  428. if($op != self::$operators['equals'])
  429. {
  430. if(
  431. !is_array($this->_conditions[$fieldName]) ||
  432. count(array_diff(array_keys($this->_conditions[$fieldName]), array_values(self::$operators))) > 0
  433. )
  434. {
  435. $this->_conditions[$fieldName] = array();
  436. }
  437. $this->_conditions[$fieldName][$op] = $value;
  438. }
  439. else
  440. $this->_conditions[$fieldName] = $value;
  441. }
  442. return $this;
  443. }
  444. }