EMongoCursor.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <?php
  2. /**
  3. * EMongoCursor
  4. *
  5. * Represents the Yii edition to the MongoCursor and allows for lazy loading of objects.
  6. *
  7. * This class does not support eager loading by default, in order to use eager loading you should look into using this
  8. * classes reponse with iterator_to_array().
  9. *
  10. * I did try originally to make this into a active data provider and use this for two fold operations but the cactivedataprovider would extend
  11. * a lot for the cursor and the two took quite different constructors.
  12. */
  13. class EMongoCursor implements Iterator, Countable
  14. {
  15. /**
  16. * @var array|EMongoCriteria
  17. */
  18. public $criteria = array();
  19. /**
  20. * @var string
  21. */
  22. public $modelClass;
  23. /**
  24. * @var EMongoDocument
  25. */
  26. public $model;
  27. /**
  28. * @var array|MongoCursor|EMongoDocument[]
  29. */
  30. private $cursor = array();
  31. /**
  32. * @var EMongoDocument
  33. */
  34. private $current;
  35. /**
  36. * This denotes a partial cursor which in turn will transpose onto the active record
  37. * to state a partial document. If any projection is supplied this will result in true since
  38. * I cannot detect if you are projecting the whole document or not...THERE IS NO PRE-DEFINED SCHEMA
  39. * @var boolean
  40. */
  41. private $partial = false;
  42. private $run = false;
  43. private $fromCache = false;
  44. private $cachedArray = array();
  45. /**
  46. * The cursor constructor
  47. * @param string|EMongoDocument $modelClass - The class name for the active record
  48. * @param array|MongoCursor|EMongoCriteria $criteria - Either a condition array (without sort,limit and skip) or a MongoCursor Object
  49. * @param array $fields
  50. */
  51. public function __construct($modelClass, $criteria = array(), $fields = array())
  52. {
  53. // If $fields has something in it
  54. if(!empty($fields)){
  55. $this->partial = true;
  56. }
  57. if(is_string($modelClass)){
  58. $this->modelClass = $modelClass;
  59. $this->model = EMongoDocument::model($this->modelClass);
  60. }elseif($modelClass instanceof EMongoDocument){
  61. $this->modelClass = get_class($modelClass);
  62. $this->model = $modelClass;
  63. }
  64. if($criteria instanceof MongoCursor){
  65. $this->cursor = $criteria;
  66. $this->cursor->reset();
  67. }elseif($criteria instanceof EMongoCriteria){
  68. $this->criteria = $criteria;
  69. $this->cursor = $this->model->getCollection()->find($criteria->condition, $criteria->project)->sort($criteria->sort);
  70. if($criteria->skip > 0){
  71. $this->cursor->skip($criteria->skip);
  72. }
  73. if($criteria->limit > 0){
  74. $this->cursor->limit($criteria->limit);
  75. }
  76. }else{
  77. // Then we are doing an active query
  78. $this->criteria = $criteria;
  79. $this->cursor = $this->model->getCollection()->find($criteria, $fields);
  80. }
  81. }
  82. /**
  83. * If we call a function that is not implemented here we try and pass the method onto
  84. * the MongoCursor class, otherwise we produce the error that normally appears
  85. *
  86. * @param string $method
  87. * @param array $params
  88. * @return mixed
  89. * @throws EMongoException
  90. */
  91. public function __call($method, $params = array())
  92. {
  93. if($this->cursor() instanceof MongoCursor && method_exists($this->cursor(), $method)){
  94. return call_user_func_array(array($this->cursor(), $method), $params);
  95. }
  96. throw new EMongoException(Yii::t('yii', 'Call to undefined function {method} on the cursor', array('{method}' => $method)));
  97. }
  98. /**
  99. * Holds the MongoCursor
  100. * @return array|MongoCursor
  101. */
  102. public function cursor()
  103. {
  104. return $this->cursor;
  105. }
  106. /**
  107. * Get next doc in cursor
  108. * @return EMongoDocument|null
  109. */
  110. public function getNext()
  111. {
  112. if(!$this->fromCache){
  113. if($c = $this->cursor()->getNext()){
  114. return $this->current = $this->model->populateRecord($c, true, $this->partial);
  115. }
  116. }else{
  117. if($c = $this->next()){
  118. return $this->current = $this->model->populateRecord($c, true, $this->partial);
  119. }
  120. }
  121. return null;
  122. }
  123. /**
  124. * Gets the active record for the current row
  125. * @return EMongoDocument|mixed
  126. * @throws EMongoException
  127. */
  128. public function current()
  129. {
  130. if(!$this->run){
  131. if(
  132. $this->model->getDbConnection()->queryCachingCount > 0
  133. && $this->model->getDbConnection()->queryCachingDuration > 0
  134. && $this->model->getDbConnection()->queryCacheID !== false
  135. && ($cache = Yii::app()->getComponent($this->model->getDbConnection()->queryCacheID)) !== null
  136. ){
  137. $this->model->getDbConnection()->queryCachingCount--;
  138. $info = $this->cursor()->info();
  139. $cacheKey =
  140. 'yii:dbquery' . $this->model->getDbConnection()->server . ':' . $this->model->getDbConnection()->db
  141. . ':' . $this->model->getDbConnection()->getSerialisedQuery(
  142. is_array($info['query']) && isset($info['query']['$query']) ? $info['query']['$query'] : array(),
  143. $info['fields'],
  144. is_array($info['query']) && isset($info['query']['$orderby']) ? $info['query']['$orderby'] : array(),
  145. $info['skip'],
  146. $info['limit']
  147. )
  148. . ':' . $this->model->getCollection();
  149. if(($result = $cache->get($cacheKey)) !== false){
  150. Yii::trace('Query result found in cache', 'extensions.MongoYii.EMongoDocument');
  151. $this->cachedArray = $result;
  152. $this->fromCache = true;
  153. }else{
  154. $this->cachedArray = iterator_to_array($this->cursor);
  155. }
  156. }
  157. if(isset($cache, $cacheKey)){
  158. $cache->set(
  159. $cacheKey,
  160. $this->cachedArray,
  161. $this->model->getDbConnection()->queryCachingDuration,
  162. $this->model->getDbConnection()->queryCachingDependency
  163. );
  164. $this->fromCache = true;
  165. }
  166. $this->run = true;
  167. }
  168. if($this->model === null){
  169. throw new EMongoException(Yii::t('yii', 'The MongoCursor must have a model'));
  170. }
  171. if($this->fromCache){
  172. return $this->current = $this->model->populateRecord(current($this->cachedArray), true, $this->partial);
  173. }
  174. return $this->current = $this->model->populateRecord($this->cursor()->current(), true, $this->partial);
  175. }
  176. /**
  177. * Counts the records returned by the criteria. By default this will not take skip and limit into account
  178. * you can add inject true as the first and only parameter to enable MongoDB to take those offsets into
  179. * consideration.
  180. *
  181. * @param bool $takeSkip
  182. * @return int
  183. */
  184. public function count($takeSkip = false /* Was true originally but it was to change the way the driver worked which seemed wrong */)
  185. {
  186. if($this->fromCache){
  187. return count($this->cachedArray);
  188. }
  189. return $this->cursor()->count($takeSkip);
  190. }
  191. /**
  192. * Set SlaveOkay
  193. * @param bool $val
  194. * @return EMongoCursor
  195. */
  196. public function slaveOkay($val = true)
  197. {
  198. $this->cursor()->slaveOkay($val);
  199. return $this;
  200. }
  201. /**
  202. * Set sort fields
  203. * @param array $fields
  204. * @return EMongoCursor
  205. */
  206. public function sort(array $fields)
  207. {
  208. $this->cursor()->sort($fields);
  209. return $this;
  210. }
  211. /**
  212. * Set skip
  213. * @param int $num
  214. * @return EMongoCursor
  215. */
  216. public function skip($num = 0)
  217. {
  218. $this->cursor()->skip($num);
  219. return $this;
  220. }
  221. /**
  222. * Set limit
  223. * @param int $num
  224. * @return EMongoCursor
  225. */
  226. public function limit($num = 0)
  227. {
  228. $this->cursor()->limit($num);
  229. return $this;
  230. }
  231. public function timeout($ms)
  232. {
  233. $this->cursor()->timeout($ms);
  234. return $this;
  235. }
  236. /**
  237. * Reset the MongoCursor to the beginning
  238. * @return EMongoCursor
  239. */
  240. public function rewind()
  241. {
  242. $this->run = false;
  243. $this->cursor()->rewind();
  244. return $this;
  245. }
  246. /**
  247. * Get the current key (_id)
  248. * @return mixed|string
  249. */
  250. public function key()
  251. {
  252. if($this->fromCache){
  253. return key($this->cachedArray);
  254. }
  255. return $this->cursor()->key();
  256. }
  257. /**
  258. * Move the pointer forward
  259. */
  260. public function next()
  261. {
  262. if($this->fromCache){
  263. return next($this->cachedArray);
  264. }
  265. $this->cursor()->next();
  266. }
  267. /**
  268. * Check if this position is a valid one in the cursor
  269. * @return bool
  270. */
  271. public function valid()
  272. {
  273. if($this->fromCache){
  274. return array_key_exists(key($this->cachedArray), $this->cachedArray);
  275. }
  276. return $this->cursor()->valid();
  277. }
  278. }