AbstractCursor.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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 MongoDB\Collection;
  17. use MongoDB\Driver\Cursor;
  18. use MongoDB\Driver\ReadPreference;
  19. /**
  20. * @internal
  21. */
  22. abstract class AbstractCursor
  23. {
  24. /**
  25. * @var int
  26. */
  27. protected $batchSize;
  28. /**
  29. * @var Collection
  30. */
  31. protected $collection;
  32. /**
  33. * @var \MongoClient
  34. */
  35. protected $connection;
  36. /**
  37. * @var Cursor
  38. */
  39. protected $cursor;
  40. /**
  41. * @var \MongoDB\Database
  42. */
  43. protected $db;
  44. /**
  45. * @var \IteratorIterator
  46. */
  47. protected $iterator;
  48. /**
  49. * @var string
  50. */
  51. protected $ns;
  52. /**
  53. * @var array
  54. */
  55. protected $optionNames = [
  56. 'batchSize',
  57. 'readPreference',
  58. ];
  59. /**
  60. * @var array
  61. */
  62. protected $readPreference = [];
  63. /**
  64. * @return Cursor
  65. */
  66. abstract protected function ensureCursor();
  67. /**
  68. * @return array
  69. */
  70. abstract protected function getCursorInfo();
  71. /**
  72. * Create a new cursor
  73. * @link http://www.php.net/manual/en/mongocursor.construct.php
  74. * @param \MongoClient $connection Database connection.
  75. * @param string $ns Full name of database and collection.
  76. */
  77. public function __construct(\MongoClient $connection, $ns)
  78. {
  79. $this->connection = $connection;
  80. $this->ns = $ns;
  81. $nsParts = explode('.', $ns);
  82. $db = array_shift($nsParts);
  83. $this->db = $connection->selectDB($db)->getDb();
  84. $this->collection = $connection->selectCollection($db, implode('.', $nsParts))->getCollection();
  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::convertObjectToLegacyArray($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. * Get the read preference for this query
  163. * @link http://www.php.net/manual/en/mongocursor.getreadpreference.php
  164. * @return array
  165. */
  166. public function getReadPreference()
  167. {
  168. return $this->readPreference;
  169. }
  170. /**
  171. * @return array
  172. */
  173. public function info()
  174. {
  175. return $this->getCursorInfo() + $this->getIterationInfo();
  176. }
  177. /**
  178. * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php
  179. * @param string $readPreference
  180. * @param array $tags
  181. * @return $this Returns this cursor.
  182. */
  183. public function setReadPreference($readPreference, $tags = null)
  184. {
  185. $availableReadPreferences = [
  186. \MongoClient::RP_PRIMARY,
  187. \MongoClient::RP_PRIMARY_PREFERRED,
  188. \MongoClient::RP_SECONDARY,
  189. \MongoClient::RP_SECONDARY_PREFERRED,
  190. \MongoClient::RP_NEAREST
  191. ];
  192. if (! in_array($readPreference, $availableReadPreferences)) {
  193. trigger_error("The value '$readPreference' is not valid as read preference type", E_WARNING);
  194. return $this;
  195. }
  196. if ($readPreference == \MongoClient::RP_PRIMARY && count($tags)) {
  197. trigger_error("You can't use read preference tags with a read preference of PRIMARY", E_WARNING);
  198. return $this;
  199. }
  200. $this->readPreference = [
  201. 'type' => $readPreference,
  202. 'tagsets' => $tags
  203. ];
  204. return $this;
  205. }
  206. /**
  207. * Sets a client-side timeout for this query
  208. * @link http://www.php.net/manual/en/mongocursor.timeout.php
  209. * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever.
  210. * @return $this Returns this cursor
  211. */
  212. public function timeout($ms)
  213. {
  214. $this->notImplemented();
  215. }
  216. /**
  217. * Applies all options set on the cursor, overwriting any options that have already been set
  218. *
  219. * @param array $optionNames Array of option names to be applied (will be read from properties)
  220. * @return array
  221. */
  222. protected function getOptions($optionNames = null)
  223. {
  224. $options = [];
  225. if ($optionNames === null) {
  226. $optionNames = $this->optionNames;
  227. }
  228. foreach ($optionNames as $option) {
  229. $converter = 'convert' . ucfirst($option);
  230. $value = method_exists($this, $converter) ? $this->$converter() : $this->$option;
  231. if ($value === null) {
  232. continue;
  233. }
  234. $options[$option] = $value;
  235. }
  236. return $options;
  237. }
  238. /**
  239. * @return ReadPreference|null
  240. */
  241. protected function convertReadPreference()
  242. {
  243. $type = array_key_exists('type', $this->readPreference) ? $this->readPreference['type'] : null;
  244. if ($type === null) {
  245. return null;
  246. }
  247. switch ($type) {
  248. case \MongoClient::RP_PRIMARY_PREFERRED:
  249. $mode = ReadPreference::RP_PRIMARY_PREFERRED;
  250. break;
  251. case \MongoClient::RP_SECONDARY:
  252. $mode = ReadPreference::RP_SECONDARY;
  253. break;
  254. case \MongoClient::RP_SECONDARY_PREFERRED:
  255. $mode = ReadPreference::RP_SECONDARY_PREFERRED;
  256. break;
  257. case \MongoClient::RP_NEAREST:
  258. $mode = ReadPreference::RP_NEAREST;
  259. break;
  260. default:
  261. $mode = ReadPreference::RP_PRIMARY;
  262. }
  263. $tagSets = array_key_exists('tagsets', $this->readPreference) ? $this->readPreference['tagsets'] : [];
  264. return new ReadPreference($mode, $tagSets);
  265. }
  266. /**
  267. * @return \IteratorIterator
  268. */
  269. protected function ensureIterator()
  270. {
  271. if ($this->iterator === null) {
  272. $this->iterator = new \IteratorIterator($this->ensureCursor());
  273. }
  274. return $this->iterator;
  275. }
  276. /**
  277. * @throws \MongoCursorException
  278. */
  279. protected function errorIfOpened()
  280. {
  281. if ($this->cursor === null) {
  282. return;
  283. }
  284. throw new \MongoCursorException('cannot modify cursor after beginning iteration.');
  285. }
  286. /**
  287. * @return array
  288. */
  289. protected function getIterationInfo()
  290. {
  291. $iterationInfo = [
  292. 'started_iterating' => $this->cursor !== null,
  293. ];
  294. if ($this->cursor !== null) {
  295. switch ($this->cursor->getServer()->getType()) {
  296. case \MongoDB\Driver\Server::TYPE_ARBITER:
  297. $typeString = 'ARBITER';
  298. break;
  299. case \MongoDB\Driver\Server::TYPE_MONGOS:
  300. $typeString = 'MONGOS';
  301. break;
  302. case \MongoDB\Driver\Server::TYPE_PRIMARY:
  303. $typeString = 'PRIMARY';
  304. break;
  305. case \MongoDB\Driver\Server::TYPE_SECONDARY:
  306. $typeString = 'SECONDARY';
  307. break;
  308. default:
  309. $typeString = 'STANDALONE';
  310. }
  311. $iterationInfo += [
  312. 'id' => (string)$this->cursor->getId(),
  313. 'at' => null, // @todo Complete info for cursor that is iterating
  314. 'numReturned' => null, // @todo Complete info for cursor that is iterating
  315. 'server' => null, // @todo Complete info for cursor that is iterating
  316. 'host' => $this->cursor->getServer()->getHost(),
  317. 'port' => $this->cursor->getServer()->getPort(),
  318. 'connection_type_desc' => $typeString,
  319. ];
  320. }
  321. return $iterationInfo;
  322. }
  323. /**
  324. * @throws \Exception
  325. */
  326. protected function notImplemented()
  327. {
  328. throw new \Exception('Not implemented');
  329. }
  330. /**
  331. * Clears the cursor
  332. *
  333. * This is generic but implemented as protected since it's only exposed in MongoCursor
  334. */
  335. protected function reset()
  336. {
  337. $this->cursor = null;
  338. $this->iterator = null;
  339. }
  340. }