AbstractCursor.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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. namespace Alcaeus\MongoDbAdapter;
  16. use Alcaeus\MongoDbAdapter\Helper\ReadPreference;
  17. use MongoDB\Collection;
  18. use MongoDB\Driver\Cursor;
  19. /**
  20. * @internal
  21. */
  22. abstract class AbstractCursor
  23. {
  24. use ReadPreference;
  25. /**
  26. * @var int
  27. */
  28. protected $batchSize;
  29. /**
  30. * @var Collection
  31. */
  32. protected $collection;
  33. /**
  34. * @var \MongoClient
  35. */
  36. protected $connection;
  37. /**
  38. * @var Cursor
  39. */
  40. protected $cursor;
  41. /**
  42. * @var \MongoDB\Database
  43. */
  44. protected $db;
  45. /**
  46. * @var \IteratorIterator
  47. */
  48. protected $iterator;
  49. /**
  50. * @var string
  51. */
  52. protected $ns;
  53. /**
  54. * @var array
  55. */
  56. protected $optionNames = [
  57. 'batchSize',
  58. 'readPreference',
  59. ];
  60. /**
  61. * @return Cursor
  62. */
  63. abstract protected function ensureCursor();
  64. /**
  65. * @return array
  66. */
  67. abstract protected function getCursorInfo();
  68. /**
  69. * Create a new cursor
  70. * @link http://www.php.net/manual/en/mongocursor.construct.php
  71. * @param \MongoClient $connection Database connection.
  72. * @param string $ns Full name of database and collection.
  73. */
  74. public function __construct(\MongoClient $connection, $ns)
  75. {
  76. $this->connection = $connection;
  77. $this->ns = $ns;
  78. $nsParts = explode('.', $ns);
  79. $dbName = array_shift($nsParts);
  80. $collectionName = implode('.', $nsParts);
  81. $this->db = $connection->selectDB($dbName)->getDb();
  82. if ($collectionName) {
  83. $this->collection = $connection->selectCollection($dbName, $collectionName)->getCollection();
  84. }
  85. }
  86. /**
  87. * Returns the current element
  88. * @link http://www.php.net/manual/en/mongocursor.current.php
  89. * @return array
  90. */
  91. public function current()
  92. {
  93. $document = $this->ensureIterator()->current();
  94. if ($document !== null) {
  95. $document = TypeConverter::toLegacy($document);
  96. }
  97. return $document;
  98. }
  99. /**
  100. * Returns the current result's _id
  101. * @link http://www.php.net/manual/en/mongocursor.key.php
  102. * @return string The current result's _id as a string.
  103. */
  104. public function key()
  105. {
  106. return $this->ensureIterator()->key();
  107. }
  108. /**
  109. * Advances the cursor to the next result
  110. * @link http://www.php.net/manual/en/mongocursor.next.php
  111. * @throws \MongoConnectionException
  112. * @throws \MongoCursorTimeoutException
  113. * @return void
  114. */
  115. public function next()
  116. {
  117. $this->ensureIterator()->next();
  118. }
  119. /**
  120. * Returns the cursor to the beginning of the result set
  121. * @throws \MongoConnectionException
  122. * @throws \MongoCursorTimeoutException
  123. * @return void
  124. */
  125. public function rewind()
  126. {
  127. // We can recreate the cursor to allow it to be rewound
  128. $this->reset();
  129. $this->ensureIterator()->rewind();
  130. }
  131. /**
  132. * Checks if the cursor is reading a valid result.
  133. * @link http://www.php.net/manual/en/mongocursor.valid.php
  134. * @return boolean If the current result is not null.
  135. */
  136. public function valid()
  137. {
  138. return $this->ensureIterator()->valid();
  139. }
  140. /**
  141. * Limits the number of elements returned in one batch.
  142. *
  143. * @link http://docs.php.net/manual/en/mongocursor.batchsize.php
  144. * @param int $batchSize The number of results to return per batch
  145. * @return $this Returns this cursor.
  146. */
  147. public function batchSize($batchSize)
  148. {
  149. $this->batchSize = $batchSize;
  150. return $this;
  151. }
  152. /**
  153. * Checks if there are documents that have not been sent yet from the database for this cursor
  154. * @link http://www.php.net/manual/en/mongocursor.dead.php
  155. * @return boolean Returns if there are more results that have not been sent to the client, yet.
  156. */
  157. public function dead()
  158. {
  159. return $this->ensureCursor()->isDead();
  160. }
  161. /**
  162. * @return array
  163. */
  164. public function info()
  165. {
  166. return $this->getCursorInfo() + $this->getIterationInfo();
  167. }
  168. /**
  169. * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php
  170. * @param string $readPreference
  171. * @param array $tags
  172. * @return $this Returns this cursor.
  173. */
  174. public function setReadPreference($readPreference, $tags = null)
  175. {
  176. $this->setReadPreferenceFromParameters($readPreference, $tags);
  177. return $this;
  178. }
  179. /**
  180. * Sets a client-side timeout for this query
  181. * @link http://www.php.net/manual/en/mongocursor.timeout.php
  182. * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever.
  183. * @return $this Returns this cursor
  184. */
  185. public function timeout($ms)
  186. {
  187. $this->notImplemented();
  188. }
  189. /**
  190. * Applies all options set on the cursor, overwriting any options that have already been set
  191. *
  192. * @param array $optionNames Array of option names to be applied (will be read from properties)
  193. * @return array
  194. */
  195. protected function getOptions($optionNames = null)
  196. {
  197. $options = [];
  198. if ($optionNames === null) {
  199. $optionNames = $this->optionNames;
  200. }
  201. foreach ($optionNames as $option) {
  202. $converter = 'convert' . ucfirst($option);
  203. $value = method_exists($this, $converter) ? $this->$converter() : $this->$option;
  204. if ($value === null) {
  205. continue;
  206. }
  207. $options[$option] = $value;
  208. }
  209. return $options;
  210. }
  211. /**
  212. * @return \Generator
  213. */
  214. protected function ensureIterator()
  215. {
  216. if ($this->iterator === null) {
  217. // MongoDB\Driver\Cursor needs to be wrapped into a \Generator so that a valid \Iterator with working implementations of
  218. // next, current, valid, key and rewind is returned. These methods don't work if we wrap the Cursor inside an \IteratorIterator
  219. $this->iterator = $this->wrapTraversable($this->ensureCursor());
  220. }
  221. return $this->iterator;
  222. }
  223. /**
  224. * @param \Traversable $traversable
  225. * @return \Generator
  226. */
  227. private function wrapTraversable(\Traversable $traversable)
  228. {
  229. foreach ($traversable as $key => $value) {
  230. yield $key => $value;
  231. }
  232. }
  233. /**
  234. * @throws \MongoCursorException
  235. */
  236. protected function errorIfOpened()
  237. {
  238. if ($this->cursor === null) {
  239. return;
  240. }
  241. throw new \MongoCursorException('cannot modify cursor after beginning iteration.');
  242. }
  243. /**
  244. * @return array
  245. */
  246. protected function getIterationInfo()
  247. {
  248. $iterationInfo = [
  249. 'started_iterating' => $this->cursor !== null,
  250. ];
  251. if ($this->cursor !== null) {
  252. switch ($this->cursor->getServer()->getType()) {
  253. case \MongoDB\Driver\Server::TYPE_RS_ARBITER:
  254. $typeString = 'ARBITER';
  255. break;
  256. case \MongoDB\Driver\Server::TYPE_MONGOS:
  257. $typeString = 'MONGOS';
  258. break;
  259. case \MongoDB\Driver\Server::TYPE_RS_PRIMARY:
  260. $typeString = 'PRIMARY';
  261. break;
  262. case \MongoDB\Driver\Server::TYPE_RS_SECONDARY:
  263. $typeString = 'SECONDARY';
  264. break;
  265. default:
  266. $typeString = 'STANDALONE';
  267. }
  268. $iterationInfo += [
  269. 'id' => (string) $this->cursor->getId(),
  270. 'at' => null, // @todo Complete info for cursor that is iterating
  271. 'numReturned' => null, // @todo Complete info for cursor that is iterating
  272. 'server' => null, // @todo Complete info for cursor that is iterating
  273. 'host' => $this->cursor->getServer()->getHost(),
  274. 'port' => $this->cursor->getServer()->getPort(),
  275. 'connection_type_desc' => $typeString,
  276. ];
  277. }
  278. return $iterationInfo;
  279. }
  280. /**
  281. * @throws \Exception
  282. */
  283. protected function notImplemented()
  284. {
  285. throw new \Exception('Not implemented');
  286. }
  287. /**
  288. * Clears the cursor
  289. *
  290. * This is generic but implemented as protected since it's only exposed in MongoCursor
  291. */
  292. protected function reset()
  293. {
  294. $this->cursor = null;
  295. $this->iterator = null;
  296. }
  297. }