MongoCursor.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. */
  15. use Alcaeus\MongoDbAdapter\TypeConverter;
  16. use MongoDB\Collection;
  17. use MongoDB\Driver\Cursor;
  18. use MongoDB\Operation\Find;
  19. /**
  20. * Result object for database query.
  21. * @link http://www.php.net/manual/en/class.mongocursor.php
  22. */
  23. class MongoCursor implements Iterator
  24. {
  25. /**
  26. * @link http://php.net/manual/en/class.mongocursor.php#mongocursor.props.slaveokay
  27. * @static
  28. * @var bool $slaveOkay
  29. */
  30. public static $slaveOkay = FALSE;
  31. /**
  32. * @var int <p>
  33. * Set timeout in milliseconds for all database responses. Use
  34. * <em>-1</em> to wait forever. Can be overridden with
  35. * {link http://php.net/manual/en/mongocursor.timeout.php MongoCursor::timeout()}. This does not cause the
  36. * MongoDB server to cancel the operation; it only instructs the driver to
  37. * stop waiting for a response and throw a
  38. * {@link http://php.net/manual/en/class.mongocursortimeoutexception.php MongoCursorTimeoutException} after a set time.
  39. * </p>
  40. */
  41. static $timeout = 30000;
  42. /**
  43. * @var MongoClient
  44. */
  45. private $connection;
  46. /**
  47. * @var string
  48. */
  49. private $ns;
  50. /**
  51. * @var array
  52. */
  53. private $query;
  54. /**
  55. * @var
  56. */
  57. private $filter;
  58. /**
  59. * @var Collection
  60. */
  61. private $collection;
  62. /**
  63. * @var Cursor
  64. */
  65. private $cursor;
  66. /**
  67. * @var IteratorIterator
  68. */
  69. private $iterator;
  70. private $awaitData;
  71. private $batchSize;
  72. private $limit;
  73. private $maxTimeMS;
  74. private $noCursorTimeout;
  75. private $options = [];
  76. private $projection;
  77. private $skip;
  78. private $sort;
  79. private $tailable;
  80. /**
  81. * Create a new cursor
  82. * @link http://www.php.net/manual/en/mongocursor.construct.php
  83. * @param MongoClient $connection Database connection.
  84. * @param string $ns Full name of database and collection.
  85. * @param array $query Database query.
  86. * @param array $fields Fields to return.
  87. * @return MongoCursor Returns the new cursor
  88. */
  89. public function __construct(MongoClient $connection, $ns, array $query = array(), array $fields = array())
  90. {
  91. $this->connection = $connection;
  92. $this->ns = $ns;
  93. $this->query = $query;
  94. $this->projection = $fields;
  95. $nsParts = explode('.', $ns);
  96. $db = array_shift($nsParts);
  97. $this->collection = $connection->selectCollection($db, implode('.', $nsParts))->getCollection();
  98. }
  99. /**
  100. * Adds a top-level key/value pair to a query
  101. * @link http://www.php.net/manual/en/mongocursor.addoption.php
  102. * @param string $key Fieldname to add.
  103. * @param mixed $value Value to add.
  104. * @throws MongoCursorException
  105. * @return MongoCursor Returns this cursor
  106. */
  107. public function addOption($key, $value)
  108. {
  109. $this->errorIfOpened();
  110. $this->options[$key] = $value;
  111. return $this;
  112. }
  113. /**
  114. * (PECL mongo &gt;= 1.2.11)<br/>
  115. * Sets whether this cursor will wait for a while for a tailable cursor to return more data
  116. * @param bool $wait [optional] <p>If the cursor should wait for more data to become available.</p>
  117. * @return MongoCursor Returns this cursor.
  118. */
  119. public function awaitData($wait = true)
  120. {
  121. $this->errorIfOpened();
  122. $this->awaitData = $wait;
  123. return $this;
  124. }
  125. /**
  126. * Limits the number of elements returned in one batch.
  127. *
  128. * @link http://docs.php.net/manual/en/mongocursor.batchsize.php
  129. * @param int $batchSize The number of results to return per batch
  130. * @return MongoCursor Returns this cursor.
  131. */
  132. public function batchSize($batchSize)
  133. {
  134. $this->errorIfOpened();
  135. $this->batchSize = $batchSize;
  136. return $this;
  137. }
  138. /**
  139. * Counts the number of results for this query
  140. * @link http://www.php.net/manual/en/mongocursor.count.php
  141. * @param bool $foundOnly Send cursor limit and skip information to the count function, if applicable.
  142. * @return int The number of documents returned by this cursor's query.
  143. */
  144. public function count($foundOnly = false)
  145. {
  146. if ($foundOnly && $this->cursor !== null) {
  147. return iterator_count($this->cursor);
  148. }
  149. $options = $foundOnly ? $this->applyOptions($this->options, ['skip', 'limit']) : $this->options;
  150. return $this->collection->count($this->query, $options);
  151. }
  152. /**
  153. * Returns the current element
  154. * @link http://www.php.net/manual/en/mongocursor.current.php
  155. * @return array
  156. */
  157. public function current()
  158. {
  159. $document = $this->ensureIterator()->current();
  160. if ($document !== null) {
  161. $document = TypeConverter::convertObjectToLegacyArray($document);
  162. }
  163. return $document;
  164. }
  165. /**
  166. * Checks if there are documents that have not been sent yet from the database for this cursor
  167. * @link http://www.php.net/manual/en/mongocursor.dead.php
  168. * @return boolean Returns if there are more results that have not been sent to the client, yet.
  169. */
  170. public function dead()
  171. {
  172. return $this->ensureCursor()->isDead();
  173. }
  174. /**
  175. * Execute the query
  176. * @link http://www.php.net/manual/en/mongocursor.doquery.php
  177. * @throws MongoConnectionException if it cannot reach the database.
  178. * @return void
  179. */
  180. protected function doQuery()
  181. {
  182. $this->notImplemented();
  183. }
  184. /**
  185. * Return an explanation of the query, often useful for optimization and debugging
  186. * @link http://www.php.net/manual/en/mongocursor.explain.php
  187. * @return array Returns an explanation of the query.
  188. */
  189. public function explain()
  190. {
  191. $this->notImplemented();
  192. }
  193. /**
  194. * Sets the fields for a query
  195. * @link http://www.php.net/manual/en/mongocursor.fields.php
  196. * @param array $f Fields to return (or not return).
  197. * @throws MongoCursorException
  198. * @return MongoCursor
  199. */
  200. public function fields(array $f)
  201. {
  202. $this->errorIfOpened();
  203. $this->projection = $f;
  204. return $this;
  205. }
  206. /**
  207. * Return the next object to which this cursor points, and advance the cursor
  208. * @link http://www.php.net/manual/en/mongocursor.getnext.php
  209. * @throws MongoConnectionException
  210. * @throws MongoCursorTimeoutException
  211. * @return array Returns the next object
  212. */
  213. public function getNext()
  214. {
  215. $this->next();
  216. return $this->current();
  217. }
  218. /**
  219. * (PECL mongo &gt;= 1.3.3)<br/>
  220. * @link http://www.php.net/manual/en/mongocursor.getreadpreference.php
  221. * @return array This function returns an array describing the read preference. The array contains the values <em>type</em> for the string
  222. * read preference mode (corresponding to the {@link http://www.php.net/manual/en/class.mongoclient.php MongoClient} constants), and <em>tagsets</em> containing a list of all tag set criteria. If no tag sets were specified, <em>tagsets</em> will not be present in the array.
  223. */
  224. public function getReadPreference()
  225. {
  226. $this->notImplemented();
  227. }
  228. /**
  229. * Checks if there are any more elements in this cursor
  230. * @link http://www.php.net/manual/en/mongocursor.hasnext.php
  231. * @throws MongoConnectionException
  232. * @throws MongoCursorTimeoutException
  233. * @return bool Returns true if there is another element
  234. */
  235. public function hasNext()
  236. {
  237. $this->notImplemented();
  238. }
  239. /**
  240. * Gives the database a hint about the query
  241. * @link http://www.php.net/manual/en/mongocursor.hint.php
  242. * @param array $key_pattern Indexes to use for the query.
  243. * @throws MongoCursorException
  244. * @return MongoCursor Returns this cursor
  245. */
  246. public function hint(array $key_pattern)
  247. {
  248. $this->notImplemented();
  249. }
  250. /**
  251. * Sets whether this cursor will timeout
  252. * @link http://www.php.net/manual/en/mongocursor.immortal.php
  253. * @param bool $liveForever If the cursor should be immortal.
  254. * @throws MongoCursorException
  255. * @return MongoCursor Returns this cursor
  256. */
  257. public function immortal($liveForever = true)
  258. {
  259. $this->errorIfOpened();
  260. $this->noCursorTimeout = $liveForever;
  261. return $this;
  262. }
  263. /**
  264. * Gets the query, fields, limit, and skip for this cursor
  265. * @link http://www.php.net/manual/en/mongocursor.info.php
  266. * @return array The query, fields, limit, and skip for this cursor as an associative array.
  267. */
  268. public function info()
  269. {
  270. $this->notImplemented();
  271. }
  272. /**
  273. * Returns the current result's _id
  274. * @link http://www.php.net/manual/en/mongocursor.key.php
  275. * @return string The current result's _id as a string.
  276. */
  277. public function key()
  278. {
  279. return $this->ensureIterator()->key();
  280. }
  281. /**
  282. * Limits the number of results returned
  283. * @link http://www.php.net/manual/en/mongocursor.limit.php
  284. * @param int $num The number of results to return.
  285. * @throws MongoCursorException
  286. * @return MongoCursor Returns this cursor
  287. */
  288. public function limit($num)
  289. {
  290. $this->errorIfOpened();
  291. $this->limit = $num;
  292. return $this;
  293. }
  294. /**
  295. * @param int $ms
  296. * @return $this
  297. * @throws MongoCursorException
  298. */
  299. public function maxTimeMS($ms)
  300. {
  301. $this->errorIfOpened();
  302. $this->maxTimeMs = $ms;
  303. return $this;
  304. }
  305. /**
  306. * Advances the cursor to the next result
  307. * @link http://www.php.net/manual/en/mongocursor.next.php
  308. * @throws MongoConnectionException
  309. * @throws MongoCursorTimeoutException
  310. * @return void
  311. */
  312. public function next()
  313. {
  314. $this->ensureIterator()->next();
  315. }
  316. /**
  317. * (PECL mongo &gt;= 1.2.0)<br/>
  318. * @link http://www.php.net/manual/en/mongocursor.partial.php
  319. * @param bool $okay [optional] <p>If receiving partial results is okay.</p>
  320. * @return MongoCursor Returns this cursor.
  321. */
  322. public function partial($okay = true)
  323. {
  324. $this->notImplemented();
  325. }
  326. /**
  327. * Clears the cursor
  328. * @link http://www.php.net/manual/en/mongocursor.reset.php
  329. * @return void
  330. */
  331. public function reset()
  332. {
  333. $this->cursor = null;
  334. $this->iterator = null;
  335. }
  336. /**
  337. * Returns the cursor to the beginning of the result set
  338. * @throws MongoConnectionException
  339. * @throws MongoCursorTimeoutException
  340. * @return void
  341. */
  342. public function rewind()
  343. {
  344. // Note: rewinding the cursor means recreating it internally
  345. $this->reset();
  346. $this->ensureIterator()->rewind();
  347. }
  348. /**
  349. * (PECL mongo &gt;= 1.2.1)<br/>
  350. * @link http://www.php.net/manual/en/mongocursor.setflag.php
  351. * @param int $flag <p>
  352. * Which flag to set. You can not set flag 6 (EXHAUST) as the driver does
  353. * not know how to handle them. You will get a warning if you try to use
  354. * this flag. For available flags, please refer to the wire protocol
  355. * {@link http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-OPQUERY documentation}.
  356. * </p>
  357. * @param bool $set [optional] <p>Whether the flag should be set (<b>TRUE</b>) or unset (<b>FALSE</b>).</p>
  358. * @return MongoCursor
  359. */
  360. public function setFlag($flag, $set = true )
  361. {
  362. $this->notImplemented();
  363. }
  364. /**
  365. * (PECL mongo &gt;= 1.3.3)<br/>
  366. * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php
  367. * @param string $read_preference <p>The read preference mode: MongoClient::RP_PRIMARY, MongoClient::RP_PRIMARY_PREFERRED, MongoClient::RP_SECONDARY, MongoClient::RP_SECONDARY_PREFERRED, or MongoClient::RP_NEAREST.</p>
  368. * @param array $tags [optional] <p>The read preference mode: MongoClient::RP_PRIMARY, MongoClient::RP_PRIMARY_PREFERRED, MongoClient::RP_SECONDARY, MongoClient::RP_SECONDARY_PREFERRED, or MongoClient::RP_NEAREST.</p>
  369. * @return MongoCursor Returns this cursor.
  370. */
  371. public function setReadPreference($read_preference, array $tags)
  372. {
  373. $this->notImplemented();
  374. }
  375. /**
  376. * Skips a number of results
  377. * @link http://www.php.net/manual/en/mongocursor.skip.php
  378. * @param int $num The number of results to skip.
  379. * @throws MongoCursorException
  380. * @return MongoCursor Returns this cursor
  381. */
  382. public function skip($num)
  383. {
  384. $this->errorIfOpened();
  385. $this->skip = $num;
  386. return $this;
  387. }
  388. /**
  389. * Sets whether this query can be done on a slave
  390. * This method will override the static class variable slaveOkay.
  391. * @link http://www.php.net/manual/en/mongocursor.slaveOkay.php
  392. * @param boolean $okay If it is okay to query the slave.
  393. * @throws MongoCursorException
  394. * @return MongoCursor Returns this cursor
  395. */
  396. public function slaveOkay($okay = true)
  397. {
  398. $this->notImplemented();
  399. }
  400. /**
  401. * Use snapshot mode for the query
  402. * @link http://www.php.net/manual/en/mongocursor.snapshot.php
  403. * @throws MongoCursorException
  404. * @return MongoCursor Returns this cursor
  405. */
  406. public function snapshot()
  407. {
  408. $this->notImplemented();
  409. }
  410. /**
  411. * Sorts the results by given fields
  412. * @link http://www.php.net/manual/en/mongocursor.sort.php
  413. * @param array $fields An array of fields by which to sort. Each element in the array has as key the field name, and as value either 1 for ascending sort, or -1 for descending sort
  414. * @throws MongoCursorException
  415. * @return MongoCursor Returns the same cursor that this method was called on
  416. */
  417. public function sort(array $fields)
  418. {
  419. $this->errorIfOpened();
  420. $this->sort = $fields;
  421. return $this;
  422. }
  423. /**
  424. * Sets whether this cursor will be left open after fetching the last results
  425. * @link http://www.php.net/manual/en/mongocursor.tailable.php
  426. * @param bool $tail If the cursor should be tailable.
  427. * @return MongoCursor Returns this cursor
  428. */
  429. public function tailable($tail = true)
  430. {
  431. $this->errorIfOpened();
  432. $this->tailable = $tail;
  433. return $this;
  434. }
  435. /**
  436. * Sets a client-side timeout for this query
  437. * @link http://www.php.net/manual/en/mongocursor.timeout.php
  438. * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever.
  439. * @throws MongoCursorTimeoutException
  440. * @return MongoCursor Returns this cursor
  441. */
  442. public function timeout($ms)
  443. {
  444. $this->notImplemented();
  445. }
  446. /**
  447. * Checks if the cursor is reading a valid result.
  448. * @link http://www.php.net/manual/en/mongocursor.valid.php
  449. * @return boolean If the current result is not null.
  450. */
  451. public function valid()
  452. {
  453. return $this->ensureIterator()->valid();
  454. }
  455. private function applyOptions($options, $optionNames)
  456. {
  457. foreach ($optionNames as $option) {
  458. if ($this->$option === null) {
  459. continue;
  460. }
  461. $options[$option] = $this->$option;
  462. }
  463. return $options;
  464. }
  465. /**
  466. * @return Cursor
  467. */
  468. private function ensureCursor()
  469. {
  470. if ($this->cursor === null) {
  471. $options = $this->applyOptions($this->options, ['skip', 'limit', 'sort', 'batchSize', 'projection']);
  472. if ($this->tailable) {
  473. $options['cursorType'] = $this->awaitData ? Find::TAILABLE : Find::TAILABLE_AWAIT;
  474. }
  475. $this->cursor = $this->collection->find($this->query, $options);
  476. }
  477. return $this->cursor;
  478. }
  479. private function errorIfOpened()
  480. {
  481. if ($this->cursor === null) {
  482. return;
  483. }
  484. throw new MongoCursorException('cannot modify cursor after beginning iteration.');
  485. }
  486. /**
  487. * @return IteratorIterator
  488. */
  489. private function ensureIterator()
  490. {
  491. if ($this->iterator === null) {
  492. $this->iterator = new IteratorIterator($this->ensureCursor());
  493. }
  494. return $this->iterator;
  495. }
  496. protected function notImplemented()
  497. {
  498. throw new \Exception('Not implemented');
  499. }
  500. }