├── lib ├── Alcaeus │ └── MongoDbAdapter │ │ ├── CursorIterator.php │ │ ├── TypeInterface.php │ │ ├── Helper │ │ ├── SlaveOkay.php │ │ ├── WriteConcernConverter.php │ │ ├── WriteConcern.php │ │ └── ReadPreference.php │ │ ├── ExceptionConverter.php │ │ ├── TypeConverter.php │ │ └── AbstractCursor.php └── Mongo │ ├── MongoException.php │ ├── MongoCursorException.php │ ├── MongoGridFSException.php │ ├── MongoConnectionException.php │ ├── MongoCursorTimeoutException.php │ ├── MongoProtocolException.php │ ├── MongoDuplicateKeyException.php │ ├── MongoExecutionTimeoutException.php │ ├── MongoMaxKey.php │ ├── MongoMinKey.php │ ├── MongoResultException.php │ ├── MongoDeleteBatch.php │ ├── MongoInsertBatch.php │ ├── MongoUpdateBatch.php │ ├── functions.php │ ├── MongoCursorInterface.php │ ├── MongoInt32.php │ ├── MongoInt64.php │ ├── MongoWriteConcernException.php │ ├── MongoPool.php │ ├── MongoCode.php │ ├── MongoRegex.php │ ├── MongoGridFSCursor.php │ ├── MongoTimestamp.php │ ├── MongoDBRef.php │ ├── MongoDate.php │ ├── MongoBinData.php │ ├── MongoGridFSFile.php │ ├── MongoCommandCursor.php │ ├── MongoLog.php │ ├── MongoId.php │ ├── Mongo.php │ ├── MongoWriteBatch.php │ ├── MongoClient.php │ ├── MongoCursor.php │ ├── MongoGridFS.php │ └── MongoDB.php ├── LICENSE ├── composer.json ├── CHANGELOG-1.1.md ├── README.md └── CHANGELOG-1.0.md /lib/Alcaeus/MongoDbAdapter/CursorIterator.php: -------------------------------------------------------------------------------- 1 | useIdAsKey = $useIdAsKey; 23 | } 24 | 25 | #[ReturnTypeWillChange] 26 | public function key() 27 | { 28 | if (!$this->useIdAsKey) { 29 | return parent::key(); 30 | } 31 | 32 | $current = $this->current(); 33 | 34 | if (!isset($current->_id) || (is_object($current->_id) && !$current->_id instanceof ObjectID)) { 35 | return parent::key(); 36 | } 37 | 38 | return (string) $current->_id; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/Mongo/MongoException.php: -------------------------------------------------------------------------------- 1 | (PECL mongo >= 1.5.0)

22 | */ 23 | class MongoProtocolException extends MongoException 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 alcaeus 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /lib/Alcaeus/MongoDbAdapter/TypeInterface.php: -------------------------------------------------------------------------------- 1 | (PECL mongo >= 1.5.0)

22 | * @link http://php.net/manual/en/class.mongoduplicatekeyexception.php 23 | */ 24 | class MongoDuplicateKeyException extends MongoWriteConcernException 25 | { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /lib/Mongo/MongoExecutionTimeoutException.php: -------------------------------------------------------------------------------- 1 | (PECL mongo >= 1.5.0)

22 | * @link http://php.net/manual/en/class.mongoexecutiontimeoutexception.php 23 | */ 24 | class MongoExecutionTimeoutException extends MongoException 25 | { 26 | } 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alcaeus/mongo-php-adapter", 3 | "type": "library", 4 | "description": "Adapter to provide ext-mongo interface on top of mongo-php-library", 5 | "keywords": ["mongodb", "database"], 6 | "license": "MIT", 7 | "authors": [ 8 | { "name": "alcaeus", "email": "alcaeus@alcaeus.org" }, 9 | { "name": "Olivier Lechevalier", "email": "olivier.lechevalier@gmail.com" } 10 | ], 11 | "require": { 12 | "php": "^5.6 || ^7.0 || ^8.0", 13 | "ext-ctype": "*", 14 | "ext-hash": "*", 15 | "ext-mongodb": "^1.2.0", 16 | "mongodb/mongodb": "^1.0.1" 17 | }, 18 | "require-dev": { 19 | "symfony/phpunit-bridge": "^4.4.16 || ^5.2", 20 | "squizlabs/php_codesniffer": "^3.2" 21 | }, 22 | "provide": { 23 | "ext-mongo": "1.6.14" 24 | }, 25 | "autoload": { 26 | "psr-0": { 27 | "Mongo": "lib/Mongo" 28 | }, 29 | "psr-4": { 30 | "Alcaeus\\MongoDbAdapter\\": "lib/Alcaeus/MongoDbAdapter" 31 | }, 32 | "files": [ "lib/Mongo/functions.php" ] 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { "Alcaeus\\MongoDbAdapter\\Tests\\": "tests/Alcaeus/MongoDbAdapter" } 36 | }, 37 | "extra": { 38 | "branch-version": "1.x" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/Mongo/MongoMaxKey.php: -------------------------------------------------------------------------------- 1 | (PECL mongo >= 1.3.0)

22 | * @link http://php.net/manual/en/class.mongoresultexception.php#mongoresultexception.props.document 23 | * 24 | */ 25 | class MongoResultException extends MongoException 26 | { 27 | /** 28 | * Retrieve the full result document 29 | * http://php.net/manual/en/mongoresultexception.getdocument.php 30 | * @return array

The full result document as an array, including partial data if available and additional keys.

31 | */ 32 | public function getDocument() 33 | { 34 | } 35 | 36 | public $document; 37 | } 38 | -------------------------------------------------------------------------------- /lib/Mongo/MongoDeleteBatch.php: -------------------------------------------------------------------------------- 1 | getSlaveOkayFromReadPreference(); 41 | } 42 | 43 | /** 44 | * @link http://www.php.net/manual/en/mongocollection.setslaveokay.php 45 | * @param bool $ok 46 | * @return bool 47 | */ 48 | public function setSlaveOkay($ok = true) 49 | { 50 | return $this->setReadPreferenceFromSlaveOkay($ok); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/Mongo/MongoInt32.php: -------------------------------------------------------------------------------- 1 | value = (string) $value; 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function __toString() 45 | { 46 | return (string) $this->value; 47 | } 48 | 49 | /** 50 | * Converts this MongoInt32 to a native integer 51 | * 52 | * @return int 53 | * @internal This method is not part of the ext-mongo API 54 | */ 55 | public function toBSONType() 56 | { 57 | return (int) $this->value; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/Mongo/MongoInt64.php: -------------------------------------------------------------------------------- 1 | value = (string) $value; 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function __toString() 45 | { 46 | return (string) $this->value; 47 | } 48 | 49 | /** 50 | * Converts this MongoInt64 to a native integer 51 | * 52 | * @return int 53 | * @internal This method is not part of the ext-mongo API 54 | */ 55 | public function toBSONType() 56 | { 57 | return (int) $this->value; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/Mongo/MongoWriteConcernException.php: -------------------------------------------------------------------------------- 1 | (PECL mongo >= 1.5.0)

22 | * @link http://php.net/manual/en/class.mongowriteconcernexception.php#class.mongowriteconcernexception 23 | */ 24 | class MongoWriteConcernException extends MongoCursorException 25 | { 26 | private $document; 27 | 28 | /** 29 | * MongoWriteConcernException constructor. 30 | * 31 | * @param string $message 32 | * @param int $code 33 | * @param Exception|null $previous 34 | * @param null $document 35 | * 36 | * @internal The $document parameter is not part of the ext-mongo API 37 | */ 38 | public function __construct($message = '', $code = 0, Exception $previous = null, $document = null) 39 | { 40 | parent::__construct($message, $code, $previous); 41 | 42 | $this->document = $document; 43 | } 44 | 45 | /** 46 | * Get the error document 47 | * @link http://php.net/manual/en/mongowriteconcernexception.getdocument.php 48 | * @return array

A MongoDB document, if available, as an array.

49 | */ 50 | public function getDocument() 51 | { 52 | return $this->document; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/Alcaeus/MongoDbAdapter/Helper/WriteConcernConverter.php: -------------------------------------------------------------------------------- 1 | createWriteConcernFromParameters($wstring, $wtimeout); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/Mongo/MongoPool.php: -------------------------------------------------------------------------------- 1 | getCode(); 44 | $scope = TypeConverter::toLegacy($javascript->getScope()); 45 | } 46 | 47 | $this->code = $code; 48 | $this->scope = $scope; 49 | } 50 | 51 | /** 52 | * Returns this code as a string 53 | * @return string 54 | */ 55 | public function __toString() 56 | { 57 | return $this->code; 58 | } 59 | 60 | /** 61 | * Converts this MongoCode to the new BSON JavaScript type 62 | * 63 | * @return \MongoDB\BSON\Javascript 64 | * @internal This method is not part of the ext-mongo API 65 | */ 66 | public function toBSONType() 67 | { 68 | return new \MongoDB\BSON\Javascript($this->code, !empty($this->scope) ? $this->scope : null); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/Alcaeus/MongoDbAdapter/Helper/WriteConcern.php: -------------------------------------------------------------------------------- 1 | writeConcern === null) { 43 | $this->writeConcern = new \MongoDB\Driver\WriteConcern(1); 44 | } 45 | 46 | return [ 47 | 'w' => $this->writeConcern->getW(), 48 | 'wtimeout' => $this->writeConcern->getWtimeout(), 49 | ]; 50 | } 51 | 52 | /** 53 | * @param string|int $wstring 54 | * @param int $wtimeout 55 | * @return bool 56 | */ 57 | protected function setWriteConcernFromParameters($wstring, $wtimeout = 0) 58 | { 59 | $this->writeConcern = $this->createWriteConcernFromParameters($wstring, $wtimeout); 60 | 61 | return true; 62 | } 63 | 64 | /** 65 | * @param array $writeConcernArray 66 | * @return bool 67 | */ 68 | protected function setWriteConcernFromArray($writeConcernArray) 69 | { 70 | $this->writeConcern = $this->createWriteConcernFromArray($writeConcernArray); 71 | 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/Mongo/MongoRegex.php: -------------------------------------------------------------------------------- 1 | regex = $regex->getPattern(); 45 | $this->flags = $regex->getFlags(); 46 | return; 47 | } 48 | 49 | if (! preg_match('#^/(.*)/([imxslu]*)$#', $regex, $matches)) { 50 | throw new MongoException('invalid regex', 9); 51 | } 52 | 53 | $this->regex = $matches[1]; 54 | $this->flags = $matches[2]; 55 | } 56 | 57 | /** 58 | * Returns a string representation of this regular expression. 59 | * @return string This regular expression in the form "/expr/flags". 60 | */ 61 | public function __toString() 62 | { 63 | return '/' . $this->regex . '/' . $this->flags; 64 | } 65 | 66 | /** 67 | * Converts this MongoRegex to the new BSON Regex type 68 | * 69 | * @return Regex 70 | * @internal This method is not part of the ext-mongo API 71 | */ 72 | public function toBSONType() 73 | { 74 | return new Regex($this->regex, $this->flags); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/Mongo/MongoGridFSCursor.php: -------------------------------------------------------------------------------- 1 | gridfs = $gridfs; 47 | parent::__construct($connection, $ns, $query, $fields); 48 | } 49 | 50 | /** 51 | * Returns the current file 52 | * 53 | * @link http://php.net/manual/en/mongogridfscursor.current.php 54 | * @return MongoGridFSFile The current file 55 | */ 56 | public function current() 57 | { 58 | $file = parent::current(); 59 | return ($file !== null) ? new MongoGridFSFile($this->gridfs, $file) : null; 60 | } 61 | 62 | /** 63 | * Returns the current result's filename 64 | * 65 | * @link http://php.net/manual/en/mongogridfscursor.key.php 66 | * @return string The current results filename 67 | */ 68 | public function key() 69 | { 70 | $file = $this->current(); 71 | return ($file !== null) ? (string) $file->file['_id'] : null; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/Mongo/MongoTimestamp.php: -------------------------------------------------------------------------------- 1 | :] 56 | $parts = explode(':', substr((string) $sec, 1, -1)); 57 | $this->sec = (int) $parts[1]; 58 | $this->inc = (int) $parts[0]; 59 | 60 | return; 61 | } 62 | 63 | if (func_num_args() == 0) { 64 | $sec = time(); 65 | } 66 | 67 | if (func_num_args() <= 1) { 68 | $inc = static::$globalInc; 69 | static::$globalInc++; 70 | } 71 | 72 | $this->sec = (int) $sec; 73 | $this->inc = (int) $inc; 74 | } 75 | 76 | /** 77 | * @return string 78 | */ 79 | public function __toString() 80 | { 81 | return (string) $this->sec; 82 | } 83 | 84 | /** 85 | * Converts this MongoTimestamp to the new BSON Timestamp type 86 | * 87 | * @return Timestamp 88 | * @internal This method is not part of the ext-mongo API 89 | */ 90 | public function toBSONType() 91 | { 92 | return new Timestamp($this->inc, $this->sec); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/Mongo/MongoDBRef.php: -------------------------------------------------------------------------------- 1 | $collection, 48 | static::$idKey => $id 49 | ]; 50 | 51 | if ($database !== null) { 52 | $ref['$db'] = $database; 53 | } 54 | 55 | return $ref; 56 | } 57 | 58 | /** 59 | * This not actually follow the reference, so it does not determine if it is broken or not. 60 | * It merely checks that $ref is in valid database reference format (in that it is an object or array with $ref and $id fields). 61 | * 62 | * @link http://php.net/manual/en/mongodbref.isref.php 63 | * @static 64 | * @param mixed $ref Array or object to check 65 | * @return boolean Returns true if $ref is a reference 66 | */ 67 | public static function isRef($ref) 68 | { 69 | $check = (array) $ref; 70 | 71 | return array_key_exists(static::$refKey, $check) && array_key_exists(static::$idKey, $check); 72 | } 73 | 74 | /** 75 | * Fetches the object pointed to by a reference 76 | * @link http://php.net/manual/en/mongodbref.get.php 77 | * @static 78 | * @param MongoDB $db Database to use 79 | * @param array $ref Reference to fetch 80 | * @return array|null Returns the document to which the reference refers or null if the document does not exist (the reference is broken) 81 | */ 82 | public static function get($db, $ref) 83 | { 84 | if (! static::isRef($ref)) { 85 | return null; 86 | } 87 | 88 | return $db->selectCollection($ref[static::$refKey])->findOne(['_id' => $ref[static::$idKey]]); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/Mongo/MongoDate.php: -------------------------------------------------------------------------------- 1 | sec = (int) $sec; 58 | $this->usec = (int) $this->truncateMicroSeconds($usec); 59 | } 60 | 61 | /** 62 | * Returns a string representation of this date 63 | * @return string 64 | */ 65 | public function __toString() 66 | { 67 | return (string) sprintf('%.8f', $this->truncateMicroSeconds($this->usec) / 1000000) . ' ' . $this->sec; 68 | } 69 | 70 | /** 71 | * Converts this MongoDate to the new BSON UTCDateTime type 72 | * 73 | * @return UTCDateTime 74 | * @internal This method is not part of the ext-mongo API 75 | */ 76 | public function toBSONType() 77 | { 78 | $milliSeconds = ($this->sec * 1000) + ($this->truncateMicroSeconds($this->usec) / 1000); 79 | 80 | return new UTCDateTime($milliSeconds); 81 | } 82 | 83 | /** 84 | * Returns a DateTime object representing this date 85 | * @link http://php.net/manual/en/mongodate.todatetime.php 86 | * @return DateTime 87 | */ 88 | public function toDateTime() 89 | { 90 | $datetime = new \DateTime(); 91 | $datetime->setTimezone(new \DateTimeZone("UTC")); 92 | $datetime->setTimestamp($this->sec); 93 | 94 | $microSeconds = $this->truncateMicroSeconds($this->usec); 95 | if ($microSeconds > 0) { 96 | $datetime = \DateTime::createFromFormat('Y-m-d H:i:s.u e', $datetime->format('Y-m-d H:i:s') . '.' . str_pad($microSeconds, 6, '0', STR_PAD_LEFT) . ' UTC'); 97 | } 98 | 99 | return $datetime; 100 | } 101 | 102 | /** 103 | * @param int $usec 104 | * @return int 105 | */ 106 | private function truncateMicroSeconds($usec) 107 | { 108 | return (int) floor($usec / 1000) * 1000; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/Mongo/MongoBinData.php: -------------------------------------------------------------------------------- 1 | bin = $data->getData(); 93 | $this->type = $data->getType(); 94 | } else { 95 | $this->bin = $data; 96 | $this->type = $type; 97 | } 98 | } 99 | 100 | /** 101 | * Returns the string "". To access the contents of a MongoBinData, use the bin field. 102 | * 103 | * @return string 104 | */ 105 | public function __toString() 106 | { 107 | return ''; 108 | } 109 | 110 | /** 111 | * Converts this MongoBinData to the new BSON Binary type 112 | * 113 | * @return Binary 114 | * @internal This method is not part of the ext-mongo API 115 | */ 116 | public function toBSONType() 117 | { 118 | return new Binary($this->bin, $this->type); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/Alcaeus/MongoDbAdapter/ExceptionConverter.php: -------------------------------------------------------------------------------- 1 | getPrevious() instanceof Exception\Exception) { 38 | $e = $e->getPrevious(); 39 | } 40 | 41 | $message = $e->getMessage(); 42 | $code = $e->getCode(); 43 | 44 | switch (get_class($e)) { 45 | case Exception\AuthenticationException::class: 46 | case Exception\ConnectionException::class: 47 | case Exception\ConnectionTimeoutException::class: 48 | case Exception\SSLConnectionException::class: 49 | $class = 'MongoConnectionException'; 50 | break; 51 | 52 | case Exception\BulkWriteException::class: 53 | case Exception\WriteException::class: 54 | $writeResult = $e->getWriteResult(); 55 | // attempt to retrieve write error 56 | if ($writeResult instanceof WriteResult && is_array($writeResult->getWriteErrors()) && $writeResult->getWriteErrors() !== []) { 57 | $writeError = $writeResult->getWriteErrors()[0]; 58 | if ($writeError instanceof WriteError) { 59 | $message = $writeError->getMessage(); 60 | $code = $writeError->getCode(); 61 | } 62 | } 63 | 64 | switch ($code) { 65 | // see https://github.com/mongodb/mongo-php-driver-legacy/blob/ad3ed45739e9702ae48e53ddfadc482d9c4c7e1c/cursor_shared.c#L540 66 | case 11000: 67 | case 11001: 68 | case 12582: 69 | $class = 'MongoDuplicateKeyException'; 70 | break; 71 | default: 72 | $class = 'MongoCursorException'; 73 | } 74 | break; 75 | 76 | case Exception\ExecutionTimeoutException::class: 77 | $class = 'MongoExecutionTimeoutException'; 78 | break; 79 | 80 | default: 81 | $class = $fallbackClass; 82 | } 83 | 84 | if (strpos($message, 'No suitable servers found') !== false) { 85 | return new \MongoConnectionException($message, $code, $e); 86 | } 87 | 88 | if ($message === "cannot use 'w' > 1 when a host is not replicated") { 89 | return new \MongoWriteConcernException($message, $code, $e); 90 | } 91 | 92 | return new $class($message, $code, $e); 93 | } 94 | 95 | /** 96 | * Converts an exception to 97 | * 98 | * @param Exception\Exception $e 99 | * @return array 100 | */ 101 | public static function toResultArray(Exception\Exception $e) 102 | { 103 | return [ 104 | 'ok' => 0.0, 105 | 'errmsg' => $e->getMessage(), 106 | 'code' => $e->getCode(), 107 | ]; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/Mongo/MongoGridFSFile.php: -------------------------------------------------------------------------------- 1 | gridfs = $gridfs; 43 | $this->file = $file; 44 | } 45 | 46 | /** 47 | * Returns this file's filename 48 | * @link http://php.net/manual/en/mongogridfsfile.getfilename.php 49 | * @return string Returns the filename 50 | */ 51 | public function getFilename() 52 | { 53 | return isset($this->file['filename']) ? $this->file['filename'] : null; 54 | } 55 | 56 | /** 57 | * Returns this file's size 58 | * @link http://php.net/manual/en/mongogridfsfile.getsize.php 59 | * @return int Returns this file's size 60 | */ 61 | public function getSize() 62 | { 63 | return $this->file['length']; 64 | } 65 | 66 | /** 67 | * Writes this file to the filesystem 68 | * @link http://php.net/manual/en/mongogridfsfile.write.php 69 | * @param string $filename The location to which to write the file (path+filename+extension). If none is given, the stored filename will be used. 70 | * @return int Returns the number of bytes written 71 | */ 72 | public function write($filename = null) 73 | { 74 | if ($filename === null) { 75 | $filename = $this->getFilename(); 76 | } 77 | if (empty($filename)) { 78 | $filename = 'file'; 79 | } 80 | 81 | if (! $handle = fopen($filename, 'w')) { 82 | trigger_error('Can not open the destination file', E_USER_ERROR); 83 | return 0; 84 | } 85 | 86 | $written = $this->copyToResource($handle); 87 | fclose($handle); 88 | 89 | return $written; 90 | } 91 | 92 | /** 93 | * This will load the file into memory. If the file is bigger than your memory, this will cause problems! 94 | * @link http://php.net/manual/en/mongogridfsfile.getbytes.php 95 | * @return string Returns a string of the bytes in the file 96 | */ 97 | public function getBytes() 98 | { 99 | $result = ''; 100 | foreach ($this->getChunks() as $chunk) { 101 | $result .= $chunk['data']->bin; 102 | } 103 | 104 | return $result; 105 | } 106 | 107 | /** 108 | * This method returns a stream resource that can be used to read the stored file with all file functions in PHP. 109 | * The contents of the file are pulled out of MongoDB on the fly, so that the whole file does not have to be loaded into memory first. 110 | * At most two GridFSFile chunks will be loaded in memory. 111 | * 112 | * @link http://php.net/manual/en/mongogridfsfile.getresource.php 113 | * @return resource Returns a resource that can be used to read the file with 114 | */ 115 | public function getResource() 116 | { 117 | $handle = fopen('php://temp', 'w+'); 118 | $this->copyToResource($handle); 119 | rewind($handle); 120 | 121 | return $handle; 122 | } 123 | 124 | private function copyToResource($handle) 125 | { 126 | $written = 0; 127 | foreach ($this->getChunks() as $chunk) { 128 | $written += fwrite($handle, $chunk['data']->bin); 129 | } 130 | 131 | return $written; 132 | } 133 | 134 | private function getChunks() 135 | { 136 | return $chunks = $this->gridfs->chunks->find( 137 | ['files_id' => $this->file['_id']], 138 | ['data' => 1], 139 | ['n' => 1] 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/Mongo/MongoCommandCursor.php: -------------------------------------------------------------------------------- 1 | command = $command; 41 | } 42 | 43 | /** 44 | * @param MongoClient $connection 45 | * @param string $hash 46 | * @param array $document 47 | * @return MongoCommandCursor 48 | */ 49 | public static function createFromDocument(MongoClient $connection, $hash, array $document) 50 | { 51 | throw new \Exception('Not implemented'); 52 | } 53 | 54 | /** 55 | * @return \MongoDB\Driver\Cursor 56 | */ 57 | protected function ensureCursor() 58 | { 59 | if ($this->cursor === null) { 60 | $convertedCommand = TypeConverter::fromLegacy($this->command); 61 | if (isset($convertedCommand->cursor)) { 62 | if ($convertedCommand->cursor === true || $convertedCommand->cursor === []) { 63 | $convertedCommand->cursor = new \stdClass(); 64 | } 65 | } 66 | 67 | $originalReadPreference = null; 68 | if (!$this->supportsReadPreference()) { 69 | $originalReadPreference = $this->readPreference; 70 | $this->setReadPreference(\MongoClient::RP_PRIMARY); 71 | } 72 | 73 | try { 74 | $this->cursor = $this->db->command($convertedCommand, $this->getOptions()); 75 | } finally { 76 | if ($originalReadPreference) { 77 | $this->readPreference = $originalReadPreference; 78 | } 79 | } 80 | } 81 | 82 | return $this->cursor; 83 | } 84 | 85 | /** 86 | * @return array 87 | */ 88 | protected function getCursorInfo() 89 | { 90 | return [ 91 | 'ns' => $this->ns, 92 | 'limit' => 0, 93 | 'batchSize' => $this->batchSize, 94 | 'skip' => 0, 95 | 'flags' => 0, 96 | 'query' => $this->command, 97 | 'fields' => null, 98 | ]; 99 | } 100 | 101 | /** 102 | * @return array 103 | */ 104 | protected function getIterationInfo() 105 | { 106 | $iterationInfo = parent::getIterationInfo(); 107 | 108 | if ($iterationInfo['started_iterating']) { 109 | $iterationInfo += [ 110 | 'firstBatchAt' => $iterationInfo['at'], 111 | 'firstBatchNumReturned' => $iterationInfo['numReturned'], 112 | ]; 113 | $iterationInfo['at'] = 0; 114 | $iterationInfo['numReturned'] = 0; 115 | } 116 | 117 | return $iterationInfo; 118 | } 119 | 120 | /** 121 | * @return array 122 | */ 123 | public function __sleep() 124 | { 125 | return ['command'] + parent::__sleep(); 126 | } 127 | 128 | /** 129 | * @see https://github.com/mongodb/mongo-php-driver-legacy/blob/1.6.14/db.c#L51 130 | * @return bool 131 | */ 132 | private function supportsReadPreference() 133 | { 134 | if ($this->command === []) { 135 | return false; 136 | } 137 | 138 | $firstKey = array_keys($this->command)[0]; 139 | switch ($firstKey) { 140 | case 'count': 141 | case 'group': 142 | case 'dbStats': 143 | case 'geoNear': 144 | case 'geoWalk': 145 | case 'distinct': 146 | case 'aggregate': 147 | case 'collStats': 148 | case 'geoSearch': 149 | case 'parallelCollectionScan': 150 | return true; 151 | 152 | case 'mapreduce': 153 | case 'mapReduce': 154 | return (isset($this->command['out']) && 155 | is_array($this->command['out']) && 156 | array_key_exists('inline', $this->command['out'])); 157 | 158 | default: 159 | return false; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/Alcaeus/MongoDbAdapter/Helper/ReadPreference.php: -------------------------------------------------------------------------------- 1 | readPreference === null) { 41 | $this->readPreference = new \MongoDB\Driver\ReadPreference(\MongoDB\Driver\ReadPreference::RP_PRIMARY); 42 | } 43 | 44 | $mode = $this->readPreference->getMode(); 45 | 46 | switch ($mode) { 47 | case \MongoDB\Driver\ReadPreference::RP_PRIMARY_PREFERRED: 48 | $type = \MongoClient::RP_PRIMARY_PREFERRED; 49 | break; 50 | case \MongoDB\Driver\ReadPreference::RP_SECONDARY: 51 | $type = \MongoClient::RP_SECONDARY; 52 | break; 53 | case \MongoDB\Driver\ReadPreference::RP_SECONDARY_PREFERRED: 54 | $type = \MongoClient::RP_SECONDARY_PREFERRED; 55 | break; 56 | case \MongoDB\Driver\ReadPreference::RP_NEAREST: 57 | $type = \MongoClient::RP_NEAREST; 58 | break; 59 | default: 60 | $type = \MongoClient::RP_PRIMARY; 61 | } 62 | 63 | $readPreference = ['type' => $type]; 64 | if ($this->readPreference->getTagSets() !== null && $this->readPreference->getTagSets() !== []) { 65 | $readPreference['tagsets'] = $this->readPreference->getTagSets(); 66 | } 67 | 68 | return $readPreference; 69 | } 70 | 71 | /** 72 | * @return bool 73 | */ 74 | protected function getSlaveOkayFromReadPreference() 75 | { 76 | return $this->readPreference->getMode() != \MongoDB\Driver\ReadPreference::RP_PRIMARY; 77 | } 78 | 79 | /** 80 | * @param string $readPreference 81 | * @param array $tags 82 | * @return bool 83 | */ 84 | protected function setReadPreferenceFromParameters($readPreference, $tags = null) 85 | { 86 | // @internal Passing an array for $readPreference is necessary to avoid conversion voodoo 87 | // It should not be used externally! 88 | if (is_array($readPreference)) { 89 | return $this->setReadPreferenceFromArray($readPreference); 90 | } 91 | 92 | switch ($readPreference) { 93 | case \MongoClient::RP_PRIMARY: 94 | $mode = \MongoDB\Driver\ReadPreference::RP_PRIMARY; 95 | break; 96 | case \MongoClient::RP_PRIMARY_PREFERRED: 97 | $mode = \MongoDB\Driver\ReadPreference::RP_PRIMARY_PREFERRED; 98 | break; 99 | case \MongoClient::RP_SECONDARY: 100 | $mode = \MongoDB\Driver\ReadPreference::RP_SECONDARY; 101 | break; 102 | case \MongoClient::RP_SECONDARY_PREFERRED: 103 | $mode = \MongoDB\Driver\ReadPreference::RP_SECONDARY_PREFERRED; 104 | break; 105 | case \MongoClient::RP_NEAREST: 106 | $mode = \MongoDB\Driver\ReadPreference::RP_NEAREST; 107 | break; 108 | default: 109 | trigger_error("The value '$readPreference' is not valid as read preference type", E_USER_WARNING); 110 | return false; 111 | } 112 | 113 | if ($readPreference == \MongoClient::RP_PRIMARY && !empty($tags)) { 114 | trigger_error("You can't use read preference tags with a read preference of PRIMARY", E_USER_WARNING); 115 | return false; 116 | } 117 | 118 | $this->readPreference = new \MongoDB\Driver\ReadPreference($mode, $tags); 119 | 120 | return true; 121 | } 122 | 123 | /** 124 | * @param array $readPreferenceArray 125 | * @return bool 126 | */ 127 | protected function setReadPreferenceFromArray($readPreferenceArray) 128 | { 129 | $readPreference = $readPreferenceArray['type']; 130 | $tags = isset($readPreferenceArray['tagsets']) ? $readPreferenceArray['tagsets'] : []; 131 | 132 | return $this->setReadPreferenceFromParameters($readPreference, $tags); 133 | } 134 | 135 | /** 136 | * @param bool $ok 137 | * @return bool 138 | */ 139 | protected function setReadPreferenceFromSlaveOkay($ok = true) 140 | { 141 | $result = $this->getSlaveOkayFromReadPreference(); 142 | $readPreference = new \MongoDB\Driver\ReadPreference( 143 | $ok ? \MongoDB\Driver\ReadPreference::RP_SECONDARY_PREFERRED : \MongoDB\Driver\ReadPreference::RP_PRIMARY, 144 | $ok ? $this->readPreference->getTagSets() : [] 145 | ); 146 | 147 | $this->readPreference = $readPreference; 148 | 149 | return $result; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lib/Mongo/MongoLog.php: -------------------------------------------------------------------------------- 1 | 95 | *

96 | * This function will set a callback function to be called for {@link http://www.php.net/manual/en/class.mongolog.php MongoLog} events 97 | * instead of triggering warnings. 98 | *

99 | * @link http://www.php.net/manual/en/mongolog.setcallback.php 100 | * @param callable $log_function

101 | * The function to be called on events. 102 | *

103 | *

104 | * The function should have the following prototype 105 | *

106 | * 107 | * log_function ( int $module , int $level, string $message) 108 | *
    109 | *
  • 110 | * module 111 | * 112 | *

    One of the {@link http://www.php.net/manual/en/class.mongolog.php#mongolog.constants.module MongoLog module constants}.

    113 | *
  • 114 | *
  • 115 | * level 116 | * 117 | *

    One of the {@link http://www.php.net/manual/en/class.mongolog.php#mongolog.constants.level MongoLog level constants}.

    118 | *
  • 120 | * message 121 | * 122 | *

    The log message itself.

    123 | *
      124 | * @return boolean Returns TRUE on success or FALSE on failure. 125 | */ 126 | public static function setCallback(callable $log_function) 127 | { 128 | self::$callback = $log_function; 129 | return true; 130 | } 131 | 132 | /** 133 | * This function can be used to set how verbose logging should be and the types of 134 | * activities that should be logged. Use the constants described in the MongoLog 135 | * section with bitwise operators to specify levels. 136 | * 137 | * @link http://php.net/manual/en/mongolog.setlevel.php 138 | * @static 139 | * @param int $level The levels you would like to log 140 | * @return void 141 | */ 142 | public static function setLevel($level) 143 | { 144 | self::$level = $level; 145 | } 146 | 147 | /** 148 | * This can be used to see the log level. Use the constants described in the 149 | * MongoLog section with bitwise operators to check the level. 150 | * 151 | * @link http://php.net/manual/en/mongolog.getlevel.php 152 | * @static 153 | * @return int Returns the current level 154 | */ 155 | public static function getLevel() 156 | { 157 | return self::$level; 158 | } 159 | 160 | /** 161 | * This function can be used to set which parts of the driver's functionality 162 | * should be logged. Use the constants described in the MongoLog section with 163 | * bitwise operators to specify modules. 164 | * 165 | * @link http://php.net/manual/en/mongolog.setmodule.php 166 | * @static 167 | * @param int $module The module(s) you would like to log 168 | * @return void 169 | */ 170 | public static function setModule($module) 171 | { 172 | self::$module = $module; 173 | } 174 | 175 | /** 176 | * This function can be used to see which parts of the driver's functionality are 177 | * being logged. Use the constants described in the MongoLog section with bitwise 178 | * operators to check if specific modules are being logged. 179 | * 180 | * @link http://php.net/manual/en/mongolog.getmodule.php 181 | * @static 182 | * @return int Returns the modules currently being logged 183 | */ 184 | public static function getModule() 185 | { 186 | return self::$module; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /CHANGELOG-1.1.md: -------------------------------------------------------------------------------- 1 | CHANGELOG for 1.1.x 2 | =================== 3 | 4 | This changelog references the relevant changes done in minor version updates. 5 | 6 | 1.1.11 (2019-11-11) 7 | ------------------- 8 | 9 | All issues and pull requests under this release may be found under the 10 | [1.1.11](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.11) 11 | milestone. 12 | 13 | * [#263](https://github.com/alcaeus/mongo-php-adapter/pull/263) fixes test 14 | failures on PHP 7.4. 15 | * [#262](https://github.com/alcaeus/mongo-php-adapter/pull/262) fixes a memory 16 | leak due to generators that aren't freed. 17 | 18 | 1.1.10 (2019-11-06) 19 | ------------------- 20 | 21 | All issues and pull requests under this release may be found under the 22 | [1.1.10](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.10) 23 | milestone. 24 | 25 | * [#261](https://github.com/alcaeus/mongo-php-adapter/pull/261) fixes missing 26 | interface implementations in cursor classes. 27 | * [#260](https://github.com/alcaeus/mongo-php-adapter/pull/260) fixes issues 28 | when running against MongoDB 4.2 or `ext-mongodb` 1.6. 29 | * [#259](https://github.com/alcaeus/mongo-php-adapter/pull/259) fixes issues on 30 | PHP 7.3 due to `MongoCursor` not implementing `Countable`. 31 | 32 | 1.1.9 (2019-08-07) 33 | ------------------ 34 | 35 | All issues and pull requests under this release may be found under the 36 | [1.1.9](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.9) 37 | milestone. 38 | 39 | * [#255](https://github.com/alcaeus/mongo-php-adapter/pull/255) fixes inserting 40 | documents with identifiers PHP considers empty. 41 | 42 | 1.1.8 (2019-07-14) 43 | ------------------ 44 | 45 | All issues and pull requests under this release may be found under the 46 | [1.1.8](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.8) 47 | milestone. 48 | 49 | * [#253](https://github.com/alcaeus/mongo-php-adapter/pull/253) fixes wrong 50 | handling of `ArrayObject` instances in `MongoCollection::insert`. 51 | 52 | 1.1.7 (2019-04-06) 53 | ------------------ 54 | 55 | All issues and pull requests under this release may be found under the 56 | [1.1.7](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.7) 57 | milestone. 58 | 59 | * [#250](https://github.com/alcaeus/mongo-php-adapter/pull/250) fixes type 60 | conversion when passing write concern to `MongoClient` via URL arguments. 61 | 62 | 63 | 1.1.6 (2019-02-08) 64 | ------------------ 65 | 66 | All issues and pull requests under this release may be found under the 67 | [1.1.6](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.6) 68 | milestone. 69 | 70 | * [#244](https://github.com/alcaeus/mongo-php-adapter/pull/244) fixes a null 71 | access when converting exceptions. 72 | * [#236](https://github.com/alcaeus/mongo-php-adapter/pull/236) allows using 73 | `0` as key in documents. 74 | * [#234](https://github.com/alcaeus/mongo-php-adapter/pull/234) removes an 75 | invalid attribute from phpunit.xml. 76 | 77 | 78 | 1.1.5 (2018-03-05) 79 | ----------------- 80 | 81 | All issues and pull requests under this release may be found under the 82 | [1.1.5](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.5) 83 | milestone. 84 | 85 | * [#222](https://github.com/alcaeus/mongo-php-adapter/pull/222) fixes handling 86 | of `monodb+srv` URLs in `MongoClient`. 87 | 88 | 1.1.4 (2018-01-24) 89 | ------------------ 90 | 91 | All issues and pull requests under this release may be found under the 92 | [1.1.4](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.4) 93 | milestone. 94 | 95 | * [#214](https://github.com/alcaeus/mongo-php-adapter/pull/214) fixes the 96 | return values of MongoBatch calls with unacknowledged write concerns. 97 | 98 | 1.1.3 (2017-09-24) 99 | ------------------ 100 | 101 | All issues and pull requests under this release may be found under the 102 | [1.1.3](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.3) 103 | milestone. 104 | 105 | * [#203](https://github.com/alcaeus/mongo-php-adapter/pull/203) fixes the 106 | detection of empty keys in update queries which were sometimes not properly 107 | handled. 108 | * [#187](https://github.com/alcaeus/mongo-php-adapter/pull/187) forces a 109 | primary read preference to certain commands that need to write data. 110 | * [#195](https://github.com/alcaeus/mongo-php-adapter/pull/195) fixes a wrong 111 | calculation leading to a wrong `updatedExisting` field in the result of an 112 | `update` query. 113 | * [#193](https://github.com/alcaeus/mongo-php-adapter/pull/193) fixes leaking 114 | new driver exceptions when calling `MongoClient::getHosts`. 115 | * [#191](https://github.com/alcaeus/mongo-php-adapter/pull/191) fixes cursor 116 | iteration when calling `hasNext` before resetting the cursor. 117 | * [#189](https://github.com/alcaeus/mongo-php-adapter/pull/189) fixes type 118 | conversion for a `query` passed to the `explain` command. 119 | * [#186](https://github.com/alcaeus/mongo-php-adapter/pull/186) fixes errors when 120 | using the 1.3 version of `ext-mongodb`. It also fixes an issue where new fields 121 | in `MongoDB::listCollections` were not properly reported. 122 | 123 | 1.1.2 (2017-08-04) 124 | ------------------ 125 | 126 | All issues and pull requests under this release may be found under the 127 | [1.1.2](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.2) 128 | milestone. 129 | 130 | * [#184](https://github.com/alcaeus/mongo-php-adapter/pull/184) fixes an invalid 131 | call to `count` which causes warnings on PHP 7.2. 132 | 133 | 1.1.1 (2017-06-30) 134 | ------------------ 135 | 136 | All issues and pull requests under this release may be found under the 137 | [1.1.1](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.1) 138 | milestone. 139 | 140 | * [#176](https://github.com/alcaeus/mongo-php-adapter/pull/176) fixes exception 141 | codes in `MongoGridFSException` exceptions that occur during GridFS operations. 142 | 143 | 1.1.0 (2017-05-13) 144 | ------------------ 145 | 146 | All issues and pull requests under this release may be found under the 147 | [1.1.0](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.1.0) 148 | milestone. 149 | 150 | * [#173](https://github.com/alcaeus/mongo-php-adapter/pull/173) adds tests for 151 | authentication options in `MongoClient`. 152 | * [#168](https://github.com/alcaeus/mongo-php-adapter/pull/168) adds support for 153 | `MongoCursor::explain()`. 154 | * [#128](https://github.com/alcaeus/mongo-php-adapter/pull/128) removes support 155 | for PHP 5.5. 156 | * [#127](https://github.com/alcaeus/mongo-php-adapter/pull/127) reads the `code` 157 | and `scope` properties of `MongoDB\BSON\Javascript` objects when converting them 158 | to `MongoCode` objects. 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mongo PHP Adapter 2 | 3 | [![Build Status](https://travis-ci.org/alcaeus/mongo-php-adapter.svg?branch=master)](https://travis-ci.org/alcaeus/mongo-php-adapter) 4 | [![Code Coverage](https://scrutinizer-ci.com/g/alcaeus/mongo-php-adapter/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/alcaeus/mongo-php-adapter/?branch=master) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/alcaeus/mongo-php-adapter/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/alcaeus/mongo-php-adapter/?branch=master) 6 | 7 | The Mongo PHP Adapter is a userland library designed to act as an adapter 8 | between applications relying on ext-mongo and the new driver (`ext-mongodb`). 9 | 10 | It provides the API of ext-mongo built on top of mongo-php-library, thus being 11 | compatible with PHP 7. 12 | 13 | # Goal 14 | 15 | This library aims to provide a compatibility layer for applications that rely on 16 | libraries using ext-mongo, e.g. 17 | [Doctrine MongoDB ODM](https://github.com/doctrine/mongodb-odm), but want to 18 | migrate to PHP 7 on which `ext-mongo` will not run. 19 | 20 | You should not be using this library if you do not rely on a library using 21 | `ext-mongo`. If you are starting a new project, please check out 22 | [mongodb/mongodb](https://github.com/mongodb/mongo-php-library). 23 | 24 | # Installation 25 | 26 | This library requires you to have the `mongodb` extension installed, and it 27 | conflicts with the legacy `mongo` extension. 28 | 29 | The preferred method of installing this library is with 30 | [Composer](https://getcomposer.org/) by running the following from your project 31 | root: 32 | 33 | $ composer config "platform.ext-mongo" "1.6.16" && composer require alcaeus/mongo-php-adapter 34 | 35 | The above command first marks the `mongo` extension as installed, then requires 36 | this adapter. This is to work around a bug in composer, see 37 | [composer/composer#5030](https://github.com/composer/composer/issues/5030). 38 | 39 | # Known issues 40 | 41 | ## Return values and exceptions 42 | 43 | Some methods may not throw exceptions with the same exception messages as their 44 | counterparts in `ext-mongo`. Do not rely on exception messages being the same. 45 | 46 | Methods that return a result array containing a `connectionId` field will always 47 | return `0` as connection ID. 48 | 49 | ## Errors 50 | 51 | All errors and warnings triggered by `ext-mongo` are triggered as `E_USER_WARNING` 52 | and `E_USER_ERROR` because `trigger_error` doesn't accept the `E_WARNING` and 53 | `E_USER` codes. If you rely on these error codes in your error handling routines, 54 | please update your code accordingly. 55 | 56 | ## Serialization of objects 57 | Serialization of any Mongo* objects (e.g. MongoGridFSFile, MongoCursor, etc.) 58 | will not work properly. The objects can be serialized but are not usable after 59 | unserializing them. 60 | 61 | ## Mongo 62 | 63 | - The Mongo class is deprecated and was not implemented in this library. If you 64 | are still using it please update your code to use the new classes. 65 | 66 | ## MongoLog 67 | 68 | - The [MongoLog](http://php.net/manual/en/class.mongolog.php) class does not 69 | log anything because the underlying driver does not offer a method to retrieve 70 | this data. 71 | 72 | ## MongoClient 73 | 74 | - The [connect](https://php.net/manual/en/mongoclient.connect.php) and 75 | [close](https://secure.php.net/manual/en/mongoclient.close.php) methods are not 76 | implemented because the underlying driver connects lazily and does not offer 77 | methods for connecting disconnecting. 78 | - The [getConnections](https://secure.php.net/manual/en/mongoclient.getconnections.php) 79 | method is not implemented because the underlying driver does not offer a method 80 | to retrieve this data. 81 | - The [killCursor](https://php.net/manual/en/mongoclient.killcursor.php) method 82 | is not yet implemented. 83 | 84 | ## MongoDB 85 | - The [authenticate](https://secure.php.net/manual/en/mongodb.authenticate.php) 86 | method is not supported. To connect to a database with authentication, please 87 | supply the credentials using the connection string. 88 | - The `$cmd` collection cannot be used due to an issue in the underlying driver. 89 | To run commands, use the [command](https://secure.php.net/manual/en/mongodb.command.php) 90 | method instead of querying the virtual `$cmd` collection. 91 | 92 | ## MongoCollection 93 | 94 | - The [insert](https://php.net/manual/en/mongocollection.insert.php), 95 | [batchInsert](https://php.net/manual/en/mongocollection.batchinsert.php), 96 | and [save](https://php.net/manual/en/mongocollection.save.php) 97 | methods take the first argument by reference. While the original API does not 98 | explicitely specify by-reference arguments it does add an ID field to the 99 | objects and documents given. 100 | - The [parallelCollectionScan](https://php.net/manual/en/mongocollection.parallelcollectionscan.php) 101 | method is not yet implemented. 102 | 103 | ## MongoCursor 104 | - The [info](https://php.net/manual/en/mongocursor.info.php) method does not 105 | reliably fill all fields in the cursor information. This includes the `numReturned` 106 | and `server` keys once the cursor has started iterating. The `numReturned` field 107 | will always show the same value as the `at` field. The `server` field is lacking 108 | authentication information. 109 | - The [setFlag](https://php.net/manual/en/mongocursor.setflag.php) 110 | method is not yet implemented. 111 | - The [timeout](https://php.net/manual/en/mongocursor.timeout.php) method will 112 | not change any query options. Client-side timeouts are no longer supported by 113 | the new driver. Use the maxTimeMS setting as a replacement. 114 | 115 | ## MongoCommandCursor 116 | - The [createFromDocument](https://php.net/manual/en/mongocommandcursor.createfromdocument.php) 117 | method is not yet implemented. 118 | - The [info](https://php.net/manual/en/mongocommandcursor.info.php) method does not 119 | reliably fill all fields in the cursor information. This includes the `at`, `numReturned`, 120 | `firstBatchAt` and `firstBatchNumReturned` fields. The `at` and `numReturned` 121 | fields always return 0 for compatibility to MongoCursor. The `firstBatchAt` and 122 | `firstBatchNumReturned` fields will contain the same value, which is the internal 123 | position of the iterator. 124 | 125 | # Development 126 | 127 | If you are working on patches to this driver, you can run the unit tests by following these steps from the root of the repo directory: 128 | 129 | $ composer install 130 | $ vendor/phpunit/phpunit/phpunit --verbose 131 | 132 | It assumes that the the `localhost` is running a mongod server. Here is a sample command to start mongod for these tests: 133 | 134 | $ mongod --smallfiles --fork --logpath /var/log/mongod.log --setParameter enableTestCommands=1 135 | 136 | The tests also assume PHP 5.6+ and the `ext-mongodb` extension being available. 137 | -------------------------------------------------------------------------------- /lib/Mongo/MongoId.php: -------------------------------------------------------------------------------- 1 | createObjectID($id); 42 | } 43 | 44 | /** 45 | * Check if a value is a valid ObjectId 46 | * 47 | * @link http://php.net/manual/en/mongoid.isvalid.php 48 | * @param mixed $value The value to check for validity. 49 | * @return bool 50 | */ 51 | public static function isValid($value) 52 | { 53 | if ($value instanceof ObjectID || $value instanceof MongoId) { 54 | return true; 55 | } elseif (! is_string($value)) { 56 | return false; 57 | } 58 | 59 | return (bool) preg_match('#^[a-f0-9]{24}$#i', $value); 60 | } 61 | 62 | /** 63 | * Returns a hexidecimal representation of this id 64 | * @link http://www.php.net/manual/en/mongoid.tostring.php 65 | * @return string 66 | */ 67 | public function __toString() 68 | { 69 | return (string) $this->objectID; 70 | } 71 | 72 | /** 73 | * Converts this MongoId to the new BSON ObjectID type 74 | * 75 | * @return ObjectID 76 | * @internal This method is not part of the ext-mongo API 77 | */ 78 | public function toBSONType() 79 | { 80 | return $this->objectID; 81 | } 82 | 83 | /** 84 | * @param string $name 85 | * 86 | * @return null|string 87 | */ 88 | public function __get($name) 89 | { 90 | if ($name === '$id') { 91 | return (string) $this->objectID; 92 | } 93 | 94 | return null; 95 | } 96 | 97 | /** 98 | * @param string $name 99 | * @param mixed $value 100 | */ 101 | public function __set($name, $value) 102 | { 103 | if ($name === 'id') { 104 | trigger_error("The '\$id' property is read-only", E_USER_DEPRECATED); 105 | return; 106 | } 107 | } 108 | 109 | /** 110 | * @param string $name 111 | * @return bool 112 | */ 113 | public function __isset($name) 114 | { 115 | return $name === 'id'; 116 | } 117 | 118 | /** 119 | * @param string $name 120 | */ 121 | public function __unset($name) 122 | { 123 | if ($name === 'id') { 124 | trigger_error("The '\$id' property is read-only", E_USER_DEPRECATED); 125 | return; 126 | } 127 | } 128 | 129 | /** 130 | * @return string 131 | */ 132 | public function serialize() 133 | { 134 | return (string) $this->objectID; 135 | } 136 | 137 | /** 138 | * @param string $serialized 139 | */ 140 | public function unserialize($serialized) 141 | { 142 | $this->createObjectID($serialized); 143 | } 144 | 145 | /** 146 | * Gets the incremented value to create this id 147 | * @link http://php.net/manual/en/mongoid.getinc.php 148 | * @return int Returns the incremented value used to create this MongoId. 149 | */ 150 | public function getInc() 151 | { 152 | return hexdec(substr((string) $this->objectID, -6)); 153 | } 154 | 155 | /** 156 | * (PECL mongo >= 1.0.11) 157 | * Gets the process ID 158 | * @link http://php.net/manual/en/mongoid.getpid.php 159 | * @return int Returns the PID of the MongoId. 160 | */ 161 | public function getPID() 162 | { 163 | $id = (string) $this->objectID; 164 | 165 | // PID is stored as little-endian, flip it around 166 | $pid = substr($id, 16, 2) . substr($id, 14, 2); 167 | return hexdec($pid); 168 | } 169 | 170 | /** 171 | * (PECL mongo >= 1.0.1) 172 | * Gets the number of seconds since the epoch that this id was created 173 | * @link http://www.php.net/manual/en/mongoid.gettimestamp.php 174 | * @return int 175 | */ 176 | public function getTimestamp() 177 | { 178 | return hexdec(substr((string) $this->objectID, 0, 8)); 179 | } 180 | 181 | /** 182 | * Gets the hostname being used for this machine's ids 183 | * @link http://www.php.net/manual/en/mongoid.gethostname.php 184 | * @return string 185 | */ 186 | public static function getHostname() 187 | { 188 | return gethostname(); 189 | } 190 | 191 | /** 192 | * (PECL mongo >= 1.0.8) 193 | * Create a dummy MongoId 194 | * @link http://php.net/manual/en/mongoid.set-state.php 195 | * @param array $props

      Theoretically, an array of properties used to create the new id. However, as MongoId instances have no properties, this is not used.

      196 | * @return MongoId A new id with the value "000000000000000000000000". 197 | */ 198 | public static function __set_state(array $props) 199 | { 200 | } 201 | 202 | /** 203 | * @return stdClass 204 | */ 205 | #[ReturnTypeWillChange] 206 | public function jsonSerialize() 207 | { 208 | $object = new stdClass(); 209 | $object->{'$id'} = (string) $this->objectID; 210 | return $object; 211 | } 212 | 213 | /** 214 | * @param $id 215 | * @throws MongoException 216 | */ 217 | private function createObjectID($id) 218 | { 219 | try { 220 | if (is_string($id)) { 221 | $this->objectID = new ObjectID($id); 222 | } elseif ($id instanceof self || $id instanceof ObjectID) { 223 | $this->objectID = new ObjectID((string) $id); 224 | } else { 225 | $this->objectID = new ObjectID(); 226 | } 227 | } catch (\Exception $e) { 228 | throw new MongoException('Invalid object ID', 19); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /lib/Alcaeus/MongoDbAdapter/TypeConverter.php: -------------------------------------------------------------------------------- 1 | toBSONType(); 44 | case $value instanceof BSON\Type: 45 | return $value; 46 | case $value instanceof \DateTimeInterface: 47 | return self::fromLegacy((array) $value); 48 | case is_array($value): 49 | case is_object($value): 50 | $result = []; 51 | 52 | foreach ($value as $key => $item) { 53 | $result[$key] = self::fromLegacy($item); 54 | } 55 | 56 | return self::ensureCorrectType($result, is_object($value)); 57 | default: 58 | return $value; 59 | } 60 | } 61 | 62 | /** 63 | * Converts a BSON type to the legacy types 64 | * 65 | * This method handles type conversion from ext-mongodb to ext-mongo: 66 | * - For all instances of BSON\Type it returns an object of the 67 | * corresponding legacy type (MongoId, MongoDate, etc.) 68 | * - For arrays and objects it iterates over properties and converts each 69 | * item individually 70 | * - For other types it returns the value unconverted 71 | * 72 | * @param mixed $value 73 | * @return mixed 74 | */ 75 | public static function toLegacy($value) 76 | { 77 | switch (true) { 78 | case $value instanceof BSON\Type: 79 | return self::convertBSONObjectToLegacy($value); 80 | case is_array($value): 81 | case is_object($value): 82 | $result = []; 83 | 84 | foreach ($value as $key => $item) { 85 | $result[$key] = self::toLegacy($item); 86 | } 87 | 88 | return $result; 89 | default: 90 | return $value; 91 | } 92 | } 93 | 94 | /** 95 | * Converts a projection used in find queries. 96 | * 97 | * This method handles conversion from the legacy syntax (e.g. ['x', 'y', 'z']) 98 | * to the new syntax (e.g. ['x' => true, 'y' => true, 'z' => true]). While 99 | * this was never documented, the legacy driver applied the same conversion. 100 | * 101 | * @param array $fields 102 | * @return array|null 103 | * 104 | * @throws \MongoException 105 | */ 106 | public static function convertProjection($fields) 107 | { 108 | if (! is_array($fields) || $fields === []) { 109 | return null; 110 | } 111 | 112 | if (! TypeConverter::isNumericArray($fields)) { 113 | $projection = TypeConverter::fromLegacy($fields); 114 | } else { 115 | $projection = array_fill_keys( 116 | array_map(function ($field) { 117 | if (!is_string($field)) { 118 | throw new \MongoException('field names must be strings', 8); 119 | } 120 | 121 | return $field; 122 | }, $fields), 123 | true 124 | ); 125 | } 126 | 127 | return TypeConverter::fromLegacy($projection); 128 | } 129 | 130 | /** 131 | * Helper method to find out if an array has numerical indexes 132 | * 133 | * For performance reason, this method checks the first array index only. 134 | * More thorough inspection of the array might be needed. 135 | * Note: Returns true for empty arrays to preserve compatibility with empty 136 | * lists. 137 | * 138 | * @param array $array 139 | * @return bool 140 | */ 141 | public static function isNumericArray(array $array) 142 | { 143 | if ($array === []) { 144 | return true; 145 | } 146 | 147 | $keys = array_keys($array); 148 | // array_keys gives us a clean numeric array with keys, so we expect an 149 | // array like [0 => 0, 1 => 1, 2 => 2, ..., n => n] 150 | return array_values($keys) === array_keys($keys); 151 | } 152 | 153 | /** 154 | * Converter method to convert a BSON object to its legacy type 155 | * 156 | * @param BSON\Type $value 157 | * @return mixed 158 | */ 159 | private static function convertBSONObjectToLegacy(BSON\Type $value) 160 | { 161 | switch (true) { 162 | case $value instanceof BSON\ObjectID: 163 | return new \MongoId($value); 164 | case $value instanceof BSON\Binary: 165 | return new \MongoBinData($value); 166 | case $value instanceof BSON\Javascript: 167 | return new \MongoCode($value); 168 | case $value instanceof BSON\MaxKey: 169 | return new \MongoMaxKey(); 170 | case $value instanceof BSON\MinKey: 171 | return new \MongoMinKey(); 172 | case $value instanceof BSON\Regex: 173 | return new \MongoRegex($value); 174 | case $value instanceof BSON\Timestamp: 175 | return new \MongoTimestamp($value); 176 | case $value instanceof BSON\UTCDatetime: 177 | return new \MongoDate($value); 178 | case $value instanceof Model\BSONDocument: 179 | case $value instanceof Model\BSONArray: 180 | return array_map( 181 | [self::class, 'toLegacy'], 182 | $value->getArrayCopy() 183 | ); 184 | default: 185 | return $value; 186 | } 187 | } 188 | 189 | /** 190 | * Converts all arrays with non-numeric keys to stdClass 191 | * 192 | * @param array $array 193 | * @param bool $wasObject 194 | * @return array|Model\BSONArray|Model\BSONDocument 195 | */ 196 | private static function ensureCorrectType(array $array, $wasObject = false) 197 | { 198 | if ($wasObject || ! static::isNumericArray($array)) { 199 | return new Model\BSONDocument($array); 200 | } 201 | 202 | return $array; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /lib/Mongo/Mongo.php: -------------------------------------------------------------------------------- 1 | notImplemented(); 36 | } 37 | 38 | /** 39 | * Get pool size for connection pools 40 | * 41 | * @link http://php.net/manual/en/mongo.getpoolsize.php 42 | * @return int Returns the current pool size. 43 | * 44 | * @deprecated This feature has been DEPRECATED as of version 1.2.3. Relying on this feature is highly discouraged. Please use MongoPool::getSize() instead. 45 | */ 46 | public function getPoolSize() 47 | { 48 | $this->notImplemented(); 49 | } 50 | 51 | /** 52 | * Returns the address being used by this for slaveOkay reads 53 | * 54 | * @link http://php.net/manual/en/mongo.getslave.php 55 | * @return bool The address of the secondary this connection is using for 56 | * reads. This returns NULL if this is not connected to a replica set or not yet 57 | * initialized. 58 | */ 59 | public function getSlave() 60 | { 61 | $this->notImplemented(); 62 | } 63 | 64 | /** 65 | * Get slaveOkay setting for this connection 66 | * 67 | * @link http://php.net/manual/en/mongo.getslaveokay.php 68 | * @return bool Returns the value of slaveOkay for this instance. 69 | */ 70 | public function getSlaveOkay() 71 | { 72 | $this->notImplemented(); 73 | } 74 | 75 | /** 76 | * Connects to paired database server 77 | * 78 | * @link http://www.php.net/manual/en/mongo.pairconnect.php 79 | * @throws MongoConnectionException 80 | * @return boolean 81 | * 82 | * @deprecated Pass a string of the form "mongodb://server1,server2" to the constructor instead of using this method. 83 | */ 84 | public function pairConnect() 85 | { 86 | $this->notImplemented(); 87 | } 88 | 89 | /** 90 | * Returns information about all connection pools. 91 | * 92 | * @link http://php.net/manual/en/mongo.pooldebug.php 93 | * @return array 94 | * @deprecated This feature has been DEPRECATED as of version 1.2.3. Relying on this feature is highly discouraged. Please use MongoPool::info() instead. 95 | */ 96 | public function poolDebug() 97 | { 98 | $this->notImplemented(); 99 | } 100 | 101 | /** 102 | * Change slaveOkay setting for this connection 103 | * 104 | * @link http://php.net/manual/en/mongo.setslaveokay.php 105 | * @param bool $ok 106 | * @return bool returns the former value of slaveOkay for this instance. 107 | */ 108 | public function setSlaveOkay($ok) 109 | { 110 | $this->notImplemented(); 111 | } 112 | 113 | /** 114 | * Set the size for future connection pools. 115 | * 116 | * @link http://php.net/manual/en/mongo.setpoolsize.php 117 | * @param $size

      The max number of connections future pools will be able to create. Negative numbers mean that the pool will spawn an infinite number of connections.

      118 | * @return bool Returns the former value of pool size. 119 | * @deprecated Relying on this feature is highly discouraged. Please use MongoPool::setSize() instead. 120 | */ 121 | public function setPoolSize($size) 122 | { 123 | $this->notImplemented(); 124 | } 125 | 126 | /** 127 | * Creates a persistent connection with a database server 128 | * 129 | * @link http://www.php.net/manual/en/mongo.persistconnect.php 130 | * @param string $username A username used to identify the connection. 131 | * @param string $password A password used to identify the connection. 132 | * @throws MongoConnectionException 133 | * @return boolean If the connection was successful. 134 | * @deprecated Pass array("persist" => $id) to the constructor instead of using this method. 135 | */ 136 | public function persistConnect($username = "", $password = "") 137 | { 138 | $this->notImplemented(); 139 | } 140 | 141 | /** 142 | * Creates a persistent connection with paired database servers 143 | * 144 | * @link http://www.php.net/manual/en/mongo.pairpersistconnect.php 145 | * @param string $username A username used to identify the connection. 146 | * @param string $password A password used to identify the connection. 147 | * @throws MongoConnectionException 148 | * @return boolean If the connection was successful. 149 | * @deprecated Pass "mongodb://server1,server2" and array("persist" => $id) to the constructor instead of using this method. 150 | */ 151 | public function pairPersistConnect($username = "", $password = "") 152 | { 153 | $this->notImplemented(); 154 | } 155 | 156 | /** 157 | * Connects with a database server 158 | * 159 | * @link http://www.php.net/manual/en/mongo.connectutil.php 160 | * @throws MongoConnectionException 161 | * @return boolean If the connection was successful. 162 | */ 163 | protected function connectUtil() 164 | { 165 | $this->notImplemented(); 166 | } 167 | 168 | /** 169 | * Check if there was an error on the most recent db operation performed 170 | * 171 | * @link http://www.php.net/manual/en/mongo.lasterror.php 172 | * @return array|null Returns the error, if there was one, or NULL. 173 | * @deprecated Use MongoDB::lastError() instead. 174 | */ 175 | public function lastError() 176 | { 177 | $this->notImplemented(); 178 | } 179 | 180 | /** 181 | * Checks for the last error thrown during a database operation 182 | * 183 | * @link http://www.php.net/manual/en/mongo.preverror.php 184 | * @return array Returns the error and the number of operations ago it occurred. 185 | * @deprecated Use MongoDB::prevError() instead. 186 | */ 187 | public function prevError() 188 | { 189 | $this->notImplemented(); 190 | } 191 | 192 | /** 193 | * Clears any flagged errors on the connection 194 | * 195 | * @link http://www.php.net/manual/en/mongo.reseterror.php 196 | * @return array Returns the database response. 197 | * @deprecated Use MongoDB::resetError() instead. 198 | */ 199 | public function resetError() 200 | { 201 | $this->notImplemented(); 202 | } 203 | 204 | /** 205 | * Choose a new secondary for slaveOkay reads 206 | * 207 | * @link www.php.net/manual/en/mongo.switchslave.php 208 | * @return string The address of the secondary this connection is using for reads. This may be the same as the previous address as addresses are randomly chosen. It may return only one address if only one secondary (or only the primary) is available. 209 | * @throws MongoException (error code 15) if it is called on a non-replica-set connection. It will also throw MongoExceptions if it cannot find anyone (primary or secondary) to read from (error code 16). 210 | */ 211 | public function switchSlave() 212 | { 213 | $this->notImplemented(); 214 | } 215 | 216 | /** 217 | * Creates a database error on the database. 218 | * 219 | * @link http://www.php.net/manual/en/mongo.forceerror.php 220 | * @return boolean The database response. 221 | * @deprecated Use MongoDB::forceError() instead. 222 | */ 223 | public function forceError() 224 | { 225 | $this->notImplemented(); 226 | } 227 | 228 | protected function notImplemented() 229 | { 230 | throw new \Exception('The Mongo class is deprecated and not supported through mongo-php-adapter'); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /CHANGELOG-1.0.md: -------------------------------------------------------------------------------- 1 | CHANGELOG for 1.0.x 2 | =================== 3 | 4 | This changelog references the relevant changes done in minor version updates. 5 | 6 | 1.0.11 (2017-04-27) 7 | ------------------- 8 | 9 | All issues and pull requests under this release may be found under the 10 | [1.0.11](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.11) 11 | milestone. 12 | 13 | * [#170](https://github.com/alcaeus/mongo-php-adapter/pull/170) fixes a 14 | `MongoCursor` object passing a `batchSize` of 0 by default, causing errors in 15 | sharded setups. 16 | 17 | 1.0.10 (2017-03-29) 18 | ------------------- 19 | 20 | All issues and pull requests under this release may be found under the 21 | [1.0.10](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.10) 22 | milestone. 23 | 24 | * [#163](https://github.com/alcaeus/mongo-php-adapter/pull/163) fixes an error 25 | when dealing with milliseconds with leading zeroes in `MongoDate` objects. 26 | 27 | 1.0.9 (2017-01-29) 28 | ------------------ 29 | 30 | All issues and pull requests under this release may be found under the 31 | [1.0.9](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.9) 32 | milestone. 33 | 34 | * [#157](https://github.com/alcaeus/mongo-php-adapter/pull/157) fixes a regression 35 | introduced in 1.0.8 when using query projection with numeric field names. 36 | * [#155](https://github.com/alcaeus/mongo-php-adapter/pull/155) fixes the handling 37 | of BSON types when converting legacy types to BSON types. 38 | * [#154](https://github.com/alcaeus/mongo-php-adapter/pull/154) makes the `options` 39 | parameter in `MongoDB::createCollection` optional. 40 | 41 | 1.0.8 (2017-01-11) 42 | ------------------ 43 | 44 | All issues and pull requests under this release may be found under the 45 | [1.0.8](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.8) 46 | milestone. 47 | 48 | * [#151](https://github.com/alcaeus/mongo-php-adapter/pull/151) allows using 49 | boolean values when passing a write concern option to an insert or update. While 50 | never documented, this was happily accepted by the legacy driver. 51 | * [#150](https://github.com/alcaeus/mongo-php-adapter/pull/150) adds missing 52 | options to `MongoCollection::indexInfo`. 53 | * [#149](https://github.com/alcaeus/mongo-php-adapter/pull/149) fixes calls to 54 | `MongoDB::getDBRef` with references containing a `$db` field with a different 55 | value than the current database. 56 | * [#145](https://github.com/alcaeus/mongo-php-adapter/pull/145) fixes query 57 | projections in the legacy syntax. While never documented, this was happily 58 | accepted by the legacy driver. 59 | 60 | 1.0.7 (2016-12-18) 61 | ------------------ 62 | 63 | All issues and pull requests under this release may be found under the 64 | [1.0.7](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.7) 65 | milestone. 66 | 67 | * [#139](https://github.com/alcaeus/mongo-php-adapter/pull/139) fixes a wrong 68 | error code in calls to `trigger_error`. 69 | 70 | 1.0.6 (2016-10-07) 71 | ------------------ 72 | 73 | All issues and pull requests under this release may be found under the 74 | [1.0.6](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.6) 75 | milestone. 76 | 77 | * [#126](https://github.com/alcaeus/mongo-php-adapter/pull/126) fixes a class 78 | name that was improperly capitalized. 79 | * [#130](https://github.com/alcaeus/mongo-php-adapter/pull/130) fixes JSON 80 | serialization of `MongoId` objects. 81 | 82 | 1.0.5 (2016-07-03) 83 | ------------------ 84 | 85 | All issues and pull requests under this release may be found under the 86 | [1.0.5](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.5) 87 | milestone. 88 | 89 | * [#117](https://github.com/alcaeus/mongo-php-adapter/pull/117) adds a missing 90 | flag to indexes when calling `MongoCollection::getIndexInfo`. 91 | * [#120](https://github.com/alcaeus/mongo-php-adapter/pull/120) throws the proper 92 | `MongoWriteConcernException` when encountering bulk write errors. 93 | * [#122](https://github.com/alcaeus/mongo-php-adapter/pull/122) fixes an error in 94 | `MongoCollection::findAndModify` when specifying both the `update` parameter as 95 | well as the `update` option. 96 | 97 | 1.0.4 (2016-06-22) 98 | ------------------ 99 | 100 | All issues and pull requests under this release may be found under the 101 | [1.0.4](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.4) 102 | milestone. 103 | 104 | * [#115](https://github.com/alcaeus/mongo-php-adapter/pull/115) fixes an error 105 | where using the alternate syntax for `MongoCollection::aggregate` would lead to 106 | empty aggregation pipelines 107 | * [#116](https://github.com/alcaeus/mongo-php-adapter/pull/116) fixes a bug 108 | where read preference and write concern was not applied if it was passed in the 109 | constructor. 110 | 111 | 1.0.3 (2016-04-13) 112 | ------------------ 113 | 114 | All issues and pull requests under this release may be found under the 115 | [1.0.3](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.3) 116 | milestone. 117 | 118 | * [#96](https://github.com/alcaeus/mongo-php-adapter/pull/96) fixes errors when 119 | calling `count` on a cursor that has been iterated fully. The fix removes a 120 | performance improvement when calling `count` on a cursor that has been opened. 121 | `MongoCursor::count` now always re-issues a `count` command to the server. 122 | * [#98](https://github.com/alcaeus/mongo-php-adapter/pull/98) fixes an error 123 | where using BSON types in a query projection would result in wrong results. 124 | * [#99](https://github.com/alcaeus/mongo-php-adapter/pull/99) ensures that the 125 | `sec` and `usec` properties for `MongoDate` are cast to int. 126 | 127 | 1.0.2 (2016-04-08) 128 | ------------------ 129 | 130 | All issues and pull requests under this release may be found under the 131 | [1.0.2](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.2) 132 | milestone. 133 | 134 | * [#90](https://github.com/alcaeus/mongo-php-adapter/pull/90) ensures that database 135 | and collection names are properly cast to string on creation. 136 | * [#94](https://github.com/alcaeus/mongo-php-adapter/pull/94) fixes an error in 137 | `MongoCursor::hasNext` that led to wrong data being returned. 138 | 139 | 1.0.1 (2016-04-01) 140 | ------------------ 141 | 142 | All issues and pull requests under this release may be found under the 143 | [1.0.1](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.1) 144 | milestone. 145 | 146 | * [#85](https://github.com/alcaeus/mongo-php-adapter/pull/85) fixes calls to 147 | `MongoCollection::count` using the legacy syntax of providing `skip` and `limit` 148 | arguments instead of an `options` array. 149 | * [#88](https://github.com/alcaeus/mongo-php-adapter/pull/88) fixes an error 150 | where a call to `MongoCollection::distinct` with a query did not convert legacy 151 | BSON types to the new driver types. 152 | 153 | 154 | 1.0.0 (2016-03-18) 155 | ------------------ 156 | 157 | All issues and pull requests under this release may be found under the 158 | [1.0.0](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.0) 159 | milestone. 160 | 161 | * [#74](https://github.com/alcaeus/mongo-php-adapter/pull/74) fixes running an 162 | aggregation command and returning a result document instead of a result cursor. 163 | This bug was fixed in the underlying mongo-php-library. 164 | * [#71](https://github.com/alcaeus/mongo-php-adapter/pull/71) adds checks to 165 | all class files to prevent class declarations when `ext-mongo` is already 166 | loaded and not using an autoloader. 167 | * [#72](https://github.com/alcaeus/mongo-php-adapter/pull/72) fixes wrong 168 | argument order in the constructor for the `Timestamp` type. 169 | * [#75](https://github.com/alcaeus/mongo-php-adapter/pull/75) adds a warning to 170 | `MongoCursor::timeout` to let people now cursor timeouts are no longer supported. 171 | * [#77](https://github.com/alcaeus/mongo-php-adapter/pull/77) adds support for 172 | the `update` option in `findAndModify` calls. 173 | 174 | 1.0.0-BETA1 (2016-02-17) 175 | ------------------------ 176 | 177 | All issues and pull requests under this release may be found under the 178 | [1.0.0-BETA1](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.0-BETA1) 179 | milestone. 180 | 181 | * [#52](https://github.com/alcaeus/mongo-php-adapter/pull/52) fixes behavior of 182 | `MongoCollection::update` when no update operators have been given. 183 | * [#53](https://github.com/alcaeus/mongo-php-adapter/pull/53) fixes an error 184 | where some operations would send an invalid query to the MongoDB server, 185 | causing command failures. 186 | * [#54](https://github.com/alcaeus/mongo-php-adapter/pull/54) and 187 | [#55](https://github.com/alcaeus/mongo-php-adapter/pull/55) fix the handling of 188 | documents with numeric keys. 189 | * [#56](https://github.com/alcaeus/mongo-php-adapter/pull/56) fixes the 190 | behavior of `MongoGridFS::findOne` when no results are found. 191 | * [#59](https://github.com/alcaeus/mongo-php-adapter/pull/59) adds handling for 192 | the `includeSystemCollections` parameter in `MongoDB::getCollectionInfo` and 193 | `MongoDB::getCollectionNames`. 194 | * [#62](https://github.com/alcaeus/mongo-php-adapter/pull/62) removes the 195 | manual comparison of index options to rely on the MongoDB server to decide 196 | whether an index already exists. 197 | * [#63](https://github.com/alcaeus/mongo-php-adapter/pull/63) prevents 198 | serialization of driver classes which are not serializable. 199 | 200 | 0.1.0 (2016-02-06) 201 | ------------------ 202 | 203 | Initial development release. 204 | -------------------------------------------------------------------------------- /lib/Mongo/MongoWriteBatch.php: -------------------------------------------------------------------------------- 1 | collection = $collection; 72 | $this->batchType = $batchType; 73 | $this->writeOptions = $writeOptions; 74 | } 75 | 76 | /** 77 | * Adds a write operation to a batch 78 | * 79 | * @see http://php.net/manual/en/mongowritebatch.add.php 80 | * @param array|object $item 81 | * @return boolean 82 | */ 83 | public function add($item) 84 | { 85 | if (is_object($item)) { 86 | $item = (array) $item; 87 | } 88 | 89 | $this->validate($item); 90 | $this->addItem($item); 91 | 92 | return true; 93 | } 94 | 95 | /** 96 | * Executes a batch of write operations 97 | * 98 | * @see http://php.net/manual/en/mongowritebatch.execute.php 99 | * @param array $writeOptions 100 | * @return array 101 | */ 102 | final public function execute(array $writeOptions = []) 103 | { 104 | $writeOptions += $this->writeOptions; 105 | if (! count($this->items)) { 106 | return ['ok' => true]; 107 | } 108 | 109 | if (isset($writeOptions['j'])) { 110 | trigger_error('j parameter is not supported', E_USER_WARNING); 111 | } 112 | if (isset($writeOptions['fsync'])) { 113 | trigger_error('fsync parameter is not supported', E_USER_WARNING); 114 | } 115 | 116 | $options['writeConcern'] = $this->createWriteConcernFromArray($writeOptions); 117 | if (isset($writeOptions['ordered'])) { 118 | $options['ordered'] = $writeOptions['ordered']; 119 | } 120 | 121 | try { 122 | $writeResult = $this->collection->getCollection()->bulkWrite($this->items, $options); 123 | $resultDocument = []; 124 | $ok = true; 125 | } catch (BulkWriteException $e) { 126 | $writeResult = $e->getWriteResult(); 127 | $resultDocument = ['writeErrors' => $this->convertWriteErrors($writeResult)]; 128 | $ok = false; 129 | } 130 | 131 | $this->items = []; 132 | 133 | switch ($this->batchType) { 134 | case self::COMMAND_UPDATE: 135 | if ($options['writeConcern']->getW() === 0) { 136 | $resultDocument += [ 137 | 'nMatched' => 0, 138 | 'nModified' => 0, 139 | 'nUpserted' => 0, 140 | 'ok' => true, 141 | ]; 142 | 143 | break; 144 | } 145 | 146 | $upsertedIds = []; 147 | foreach ($writeResult->getUpsertedIds() as $index => $id) { 148 | $upsertedIds[] = [ 149 | 'index' => $index, 150 | '_id' => TypeConverter::toLegacy($id) 151 | ]; 152 | } 153 | 154 | $resultDocument += [ 155 | 'nMatched' => $writeResult->getMatchedCount(), 156 | 'nModified' => $writeResult->getModifiedCount(), 157 | 'nUpserted' => $writeResult->getUpsertedCount(), 158 | 'ok' => true, 159 | ]; 160 | 161 | if (count($upsertedIds)) { 162 | $resultDocument['upserted'] = $upsertedIds; 163 | } 164 | break; 165 | 166 | case self::COMMAND_DELETE: 167 | if ($options['writeConcern']->getW() === 0) { 168 | $resultDocument += [ 169 | 'nRemoved' => 0, 170 | 'ok' => true, 171 | ]; 172 | 173 | break; 174 | } 175 | 176 | $resultDocument += [ 177 | 'nRemoved' => $writeResult->getDeletedCount(), 178 | 'ok' => true, 179 | ]; 180 | break; 181 | 182 | case self::COMMAND_INSERT: 183 | if ($options['writeConcern']->getW() === 0) { 184 | $resultDocument += [ 185 | 'nInserted' => 0, 186 | 'ok' => true, 187 | ]; 188 | 189 | break; 190 | } 191 | 192 | $resultDocument += [ 193 | 'nInserted' => $writeResult->getInsertedCount(), 194 | 'ok' => true, 195 | ]; 196 | break; 197 | } 198 | 199 | if (! $ok) { 200 | // Exception code is hardcoded to the value in ext-mongo, see 201 | // https://github.com/mongodb/mongo-php-driver-legacy/blob/ab4bc0d90e93b3f247f6bcb386d0abc8d2fa7d74/batch/write.c#L428 202 | throw new \MongoWriteConcernException('Failed write', 911, null, $resultDocument); 203 | } 204 | 205 | return $resultDocument; 206 | } 207 | 208 | private function validate(array $item) 209 | { 210 | switch ($this->batchType) { 211 | case self::COMMAND_UPDATE: 212 | if (! isset($item['q'])) { 213 | throw new Exception("Expected \$item to contain 'q' key"); 214 | } 215 | if (! isset($item['u'])) { 216 | throw new Exception("Expected \$item to contain 'u' key"); 217 | } 218 | break; 219 | 220 | case self::COMMAND_DELETE: 221 | if (! isset($item['q'])) { 222 | throw new Exception("Expected \$item to contain 'q' key"); 223 | } 224 | if (! isset($item['limit'])) { 225 | throw new Exception("Expected \$item to contain 'limit' key"); 226 | } 227 | break; 228 | } 229 | } 230 | 231 | private function addItem(array $item) 232 | { 233 | switch ($this->batchType) { 234 | case self::COMMAND_UPDATE: 235 | $method = isset($item['multi']) ? 'updateMany' : 'updateOne'; 236 | 237 | $options = []; 238 | if (isset($item['upsert']) && $item['upsert']) { 239 | $options['upsert'] = true; 240 | } 241 | 242 | $this->items[] = [$method => [TypeConverter::fromLegacy($item['q']), TypeConverter::fromLegacy($item['u']), $options]]; 243 | break; 244 | 245 | case self::COMMAND_INSERT: 246 | $this->items[] = ['insertOne' => [TypeConverter::fromLegacy($item)]]; 247 | break; 248 | 249 | case self::COMMAND_DELETE: 250 | $method = $item['limit'] === 0 ? 'deleteMany' : 'deleteOne'; 251 | 252 | $this->items[] = [$method => [TypeConverter::fromLegacy($item['q'])]]; 253 | break; 254 | } 255 | } 256 | 257 | /** 258 | * @param WriteResult $result 259 | * @return array 260 | */ 261 | private function convertWriteErrors(WriteResult $result) 262 | { 263 | $writeErrors = []; 264 | /** @var WriteError $writeError */ 265 | foreach ($result->getWriteErrors() as $writeError) { 266 | $writeErrors[] = [ 267 | 'index' => $writeError->getIndex(), 268 | 'code' => $writeError->getCode(), 269 | 'errmsg' => $writeError->getMessage(), 270 | ]; 271 | } 272 | return $writeErrors; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /lib/Alcaeus/MongoDbAdapter/AbstractCursor.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 122 | $this->ns = $ns; 123 | 124 | $nsParts = explode('.', $ns); 125 | $dbName = array_shift($nsParts); 126 | $collectionName = implode('.', $nsParts); 127 | 128 | $this->db = $connection->selectDB($dbName)->getDb(); 129 | 130 | if ($collectionName) { 131 | $this->collection = $connection->selectCollection($dbName, $collectionName)->getCollection(); 132 | } 133 | } 134 | 135 | /** 136 | * Returns the current element 137 | * @link http://www.php.net/manual/en/mongocursor.current.php 138 | * @return array 139 | */ 140 | #[ReturnTypeWillChange] 141 | public function current() 142 | { 143 | return $this->current; 144 | } 145 | 146 | /** 147 | * Returns the current result's _id 148 | * @link http://www.php.net/manual/en/mongocursor.key.php 149 | * @return string The current result's _id as a string. 150 | */ 151 | #[ReturnTypeWillChange] 152 | public function key() 153 | { 154 | return $this->key; 155 | } 156 | 157 | /** 158 | * Advances the cursor to the next result, and returns that result 159 | * @link http://www.php.net/manual/en/mongocursor.next.php 160 | * @throws \MongoConnectionException 161 | * @throws \MongoCursorTimeoutException 162 | * @return array Returns the next object 163 | */ 164 | #[ReturnTypeWillChange] 165 | public function next() 166 | { 167 | if (! $this->startedIterating) { 168 | $this->ensureIterator(); 169 | $this->startedIterating = true; 170 | } else { 171 | if ($this->cursorNeedsAdvancing) { 172 | $this->ensureIterator()->next(); 173 | } 174 | 175 | $this->cursorNeedsAdvancing = true; 176 | $this->position++; 177 | } 178 | 179 | return $this->storeIteratorState(); 180 | } 181 | 182 | /** 183 | * Returns the cursor to the beginning of the result set 184 | * @throws \MongoConnectionException 185 | * @throws \MongoCursorTimeoutException 186 | * @return void 187 | */ 188 | #[ReturnTypeWillChange] 189 | public function rewind() 190 | { 191 | // We can recreate the cursor to allow it to be rewound 192 | $this->reset(); 193 | $this->startedIterating = true; 194 | $this->position = 0; 195 | $this->ensureIterator()->rewind(); 196 | $this->storeIteratorState(); 197 | } 198 | 199 | /** 200 | * Checks if the cursor is reading a valid result. 201 | * @link http://www.php.net/manual/en/mongocursor.valid.php 202 | * @return boolean If the current result is not null. 203 | */ 204 | #[ReturnTypeWillChange] 205 | public function valid() 206 | { 207 | return $this->valid; 208 | } 209 | 210 | /** 211 | * Limits the number of elements returned in one batch. 212 | * 213 | * @link http://docs.php.net/manual/en/mongocursor.batchsize.php 214 | * @param int|null $batchSize The number of results to return per batch 215 | * @return $this Returns this cursor. 216 | */ 217 | public function batchSize($batchSize) 218 | { 219 | $this->batchSize = $batchSize; 220 | 221 | return $this; 222 | } 223 | 224 | /** 225 | * Checks if there are documents that have not been sent yet from the database for this cursor 226 | * @link http://www.php.net/manual/en/mongocursor.dead.php 227 | * @return boolean Returns if there are more results that have not been sent to the client, yet. 228 | */ 229 | public function dead() 230 | { 231 | return $this->ensureCursor()->isDead(); 232 | } 233 | 234 | /** 235 | * @return array 236 | */ 237 | public function info() 238 | { 239 | return $this->getCursorInfo() + $this->getIterationInfo(); 240 | } 241 | 242 | /** 243 | * @link http://www.php.net/manual/en/mongocursor.setreadpreference.php 244 | * @param string $readPreference 245 | * @param array $tags 246 | * @return $this Returns this cursor. 247 | */ 248 | public function setReadPreference($readPreference, $tags = null) 249 | { 250 | $this->setReadPreferenceFromParameters($readPreference, $tags); 251 | 252 | return $this; 253 | } 254 | 255 | /** 256 | * Sets a client-side timeout for this query 257 | * @link http://www.php.net/manual/en/mongocursor.timeout.php 258 | * @param int $ms The number of milliseconds for the cursor to wait for a response. By default, the cursor will wait forever. 259 | * @return $this Returns this cursor 260 | */ 261 | public function timeout($ms) 262 | { 263 | trigger_error('The ' . __METHOD__ . ' method is not implemented in mongo-php-adapter', E_USER_WARNING); 264 | return $this; 265 | } 266 | 267 | /** 268 | * Applies all options set on the cursor, overwriting any options that have already been set 269 | * 270 | * @param array $optionNames Array of option names to be applied (will be read from properties) 271 | * @return array 272 | */ 273 | protected function getOptions($optionNames = null) 274 | { 275 | $options = []; 276 | 277 | if ($optionNames === null) { 278 | $optionNames = $this->optionNames; 279 | } 280 | 281 | foreach ($optionNames as $option) { 282 | $converter = 'convert' . ucfirst($option); 283 | $value = method_exists($this, $converter) ? $this->$converter() : $this->$option; 284 | 285 | if ($value === null) { 286 | continue; 287 | } 288 | 289 | $options[$option] = $value; 290 | } 291 | 292 | return $options; 293 | } 294 | 295 | /** 296 | * @return \Iterator 297 | */ 298 | protected function ensureIterator() 299 | { 300 | if ($this->iterator === null) { 301 | $this->iterator = $this->wrapTraversable($this->ensureCursor()); 302 | $this->iterator->rewind(); 303 | } 304 | 305 | return $this->iterator; 306 | } 307 | 308 | /** 309 | * @param \Traversable $traversable 310 | * @return CursorIterator 311 | */ 312 | protected function wrapTraversable(\Traversable $traversable) 313 | { 314 | return new CursorIterator($traversable); 315 | } 316 | 317 | /** 318 | * @throws \MongoCursorException 319 | */ 320 | protected function errorIfOpened() 321 | { 322 | if ($this->cursor === null) { 323 | return; 324 | } 325 | 326 | throw new \MongoCursorException('cannot modify cursor after beginning iteration.'); 327 | } 328 | 329 | /** 330 | * @return array 331 | */ 332 | protected function getIterationInfo() 333 | { 334 | $iterationInfo = [ 335 | 'started_iterating' => $this->cursor !== null, 336 | ]; 337 | 338 | if ($this->cursor !== null) { 339 | switch ($this->cursor->getServer()->getType()) { 340 | case \MongoDB\Driver\Server::TYPE_RS_ARBITER: 341 | $typeString = 'ARBITER'; 342 | break; 343 | case \MongoDB\Driver\Server::TYPE_MONGOS: 344 | $typeString = 'MONGOS'; 345 | break; 346 | case \MongoDB\Driver\Server::TYPE_RS_PRIMARY: 347 | $typeString = 'PRIMARY'; 348 | break; 349 | case \MongoDB\Driver\Server::TYPE_RS_SECONDARY: 350 | $typeString = 'SECONDARY'; 351 | break; 352 | default: 353 | $typeString = 'STANDALONE'; 354 | } 355 | 356 | $cursorId = (string) $this->cursor->getId(); 357 | $iterationInfo += [ 358 | 'id' => (int) $cursorId, 359 | 'at' => $this->position, 360 | 'numReturned' => $this->position, // This can't be obtained from the new cursor 361 | 'server' => sprintf('%s:%d;-;.;%d', $this->cursor->getServer()->getHost(), $this->cursor->getServer()->getPort(), getmypid()), 362 | 'host' => $this->cursor->getServer()->getHost(), 363 | 'port' => $this->cursor->getServer()->getPort(), 364 | 'connection_type_desc' => $typeString, 365 | ]; 366 | } 367 | 368 | return $iterationInfo; 369 | } 370 | 371 | /** 372 | * @throws \Exception 373 | */ 374 | protected function notImplemented() 375 | { 376 | throw new \Exception('Not implemented'); 377 | } 378 | 379 | /** 380 | * Clears the cursor 381 | * 382 | * This is generic but implemented as protected since it's only exposed in MongoCursor 383 | */ 384 | protected function reset() 385 | { 386 | $this->startedIterating = false; 387 | $this->cursorNeedsAdvancing = true; 388 | $this->cursor = null; 389 | $this->iterator = null; 390 | $this->storeIteratorState(); 391 | } 392 | 393 | /** 394 | * @return array 395 | */ 396 | public function __sleep() 397 | { 398 | return ['batchSize', 'connection', 'iterator', 'ns', 'optionNames', 'position', 'startedIterating']; 399 | } 400 | 401 | /** 402 | * Stores the current cursor element. 403 | * 404 | * This is necessary because hasNext() might advance the iterator but we still 405 | * need to be able to return the current object. 406 | */ 407 | protected function storeIteratorState() 408 | { 409 | if (! $this->startedIterating) { 410 | $this->current = null; 411 | $this->key = null; 412 | $this->valid = false; 413 | return null; 414 | } 415 | 416 | $this->current = $this->ensureIterator()->current(); 417 | $this->key = $this->ensureIterator()->key(); 418 | $this->valid = $this->ensureIterator()->valid(); 419 | 420 | if ($this->current !== null) { 421 | $this->current = TypeConverter::toLegacy($this->current); 422 | } 423 | 424 | return $this->current; 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /lib/Mongo/MongoClient.php: -------------------------------------------------------------------------------- 1 | true], array $driverOptions = []) 84 | { 85 | if ($server === 'default') { 86 | $server = 'mongodb://' . self::DEFAULT_HOST . ':' . self::DEFAULT_PORT; 87 | } 88 | 89 | if (isset($options['readPreferenceTags'])) { 90 | $options['readPreferenceTags'] = [$this->getReadPreferenceTags($options['readPreferenceTags'])]; 91 | } 92 | 93 | $this->applyConnectionOptions($server, $options); 94 | 95 | $this->server = $server; 96 | if (false === strpos($this->server, '://')) { 97 | $this->server = 'mongodb://' . $this->server; 98 | } 99 | $this->client = new Client($this->server, $options, $driverOptions + ['driver' => ['name' => 'mongo-php-adapter']]); 100 | $info = $this->client->__debugInfo(); 101 | $this->manager = $info['manager']; 102 | 103 | if (isset($options['connect']) && $options['connect']) { 104 | $this->connect(); 105 | } 106 | } 107 | 108 | 109 | /** 110 | * Closes this database connection 111 | * 112 | * @link http://www.php.net/manual/en/mongoclient.close.php 113 | * @param boolean|string $connection 114 | * @return boolean If the connection was successfully closed. 115 | */ 116 | public function close($connection = null) 117 | { 118 | $this->connected = false; 119 | 120 | return false; 121 | } 122 | 123 | /** 124 | * Connects to a database server 125 | * 126 | * @link http://www.php.net/manual/en/mongoclient.connect.php 127 | * 128 | * @throws MongoConnectionException 129 | * @return boolean If the connection was successful. 130 | */ 131 | public function connect() 132 | { 133 | $this->connected = true; 134 | 135 | return true; 136 | } 137 | 138 | /** 139 | * Drops a database 140 | * 141 | * @link http://www.php.net/manual/en/mongoclient.dropdb.php 142 | * @param mixed $db The database to drop. Can be a MongoDB object or the name of the database. 143 | * @return array The database response. 144 | * @deprecated Use MongoDB::drop() instead. 145 | */ 146 | public function dropDB($db) 147 | { 148 | return $this->selectDB($db)->drop(); 149 | } 150 | 151 | /** 152 | * Gets a database 153 | * 154 | * @link http://php.net/manual/en/mongoclient.get.php 155 | * @param string $dbname The database name. 156 | * @return MongoDB The database name. 157 | */ 158 | public function __get($dbname) 159 | { 160 | return $this->selectDB($dbname); 161 | } 162 | 163 | /** 164 | * Gets the client for this object 165 | * 166 | * @internal This part is not of the ext-mongo API and should not be used 167 | * @return Client 168 | */ 169 | public function getClient() 170 | { 171 | return $this->client; 172 | } 173 | 174 | /** 175 | * Get connections 176 | * 177 | * Returns an array of all open connections, and information about each of the servers 178 | * 179 | * @return array 180 | */ 181 | public static function getConnections() 182 | { 183 | return []; 184 | } 185 | 186 | /** 187 | * Get hosts 188 | * 189 | * This method is only useful with a connection to a replica set. It returns the status of all of the hosts in the 190 | * set. Without a replica set, it will just return an array with one element containing the host that you are 191 | * connected to. 192 | * 193 | * @return array 194 | */ 195 | public function getHosts() 196 | { 197 | $this->forceConnect(); 198 | 199 | $results = []; 200 | 201 | try { 202 | $servers = $this->manager->getServers(); 203 | } catch (\MongoDB\Driver\Exception\Exception $e) { 204 | throw ExceptionConverter::toLegacy($e); 205 | } 206 | 207 | foreach ($servers as $server) { 208 | $key = sprintf('%s:%d;-;.;%d', $server->getHost(), $server->getPort(), getmypid()); 209 | $info = $server->getInfo(); 210 | 211 | switch ($server->getType()) { 212 | case \MongoDB\Driver\Server::TYPE_RS_PRIMARY: 213 | $state = 1; 214 | break; 215 | case \MongoDB\Driver\Server::TYPE_RS_SECONDARY: 216 | $state = 2; 217 | break; 218 | default: 219 | $state = 0; 220 | } 221 | 222 | $results[$key] = [ 223 | 'host' => $server->getHost(), 224 | 'port' => $server->getPort(), 225 | 'health' => 1, 226 | 'state' => $state, 227 | 'ping' => $server->getLatency(), 228 | 'lastPing' => null, 229 | ]; 230 | } 231 | 232 | return $results; 233 | } 234 | 235 | /** 236 | * Kills a specific cursor on the server 237 | * 238 | * @link http://www.php.net/manual/en/mongoclient.killcursor.php 239 | * @param string $server_hash The server hash that has the cursor. 240 | * @param int|MongoInt64 $id The ID of the cursor to kill. 241 | * @return bool 242 | */ 243 | public function killCursor($server_hash, $id) 244 | { 245 | $this->notImplemented(); 246 | } 247 | 248 | /** 249 | * Lists all of the databases available 250 | * 251 | * @link http://php.net/manual/en/mongoclient.listdbs.php 252 | * @return array Returns an associative array containing three fields. The first field is databases, which in turn contains an array. Each element of the array is an associative array corresponding to a database, giving the database's name, size, and if it's empty. The other two fields are totalSize (in bytes) and ok, which is 1 if this method ran successfully. 253 | */ 254 | public function listDBs() 255 | { 256 | try { 257 | $databaseInfoIterator = $this->client->listDatabases(); 258 | } catch (\MongoDB\Driver\Exception\Exception $e) { 259 | throw ExceptionConverter::toLegacy($e); 260 | } 261 | 262 | $databases = [ 263 | 'databases' => [], 264 | 'totalSize' => 0, 265 | 'ok' => 1.0, 266 | ]; 267 | 268 | foreach ($databaseInfoIterator as $databaseInfo) { 269 | $databases['databases'][] = [ 270 | 'name' => $databaseInfo->getName(), 271 | 'empty' => $databaseInfo->isEmpty(), 272 | 'sizeOnDisk' => $databaseInfo->getSizeOnDisk(), 273 | ]; 274 | $databases['totalSize'] += $databaseInfo->getSizeOnDisk(); 275 | } 276 | 277 | return $databases; 278 | } 279 | 280 | /** 281 | * Gets a database collection 282 | * 283 | * @link http://www.php.net/manual/en/mongoclient.selectcollection.php 284 | * @param string $db The database name. 285 | * @param string $collection The collection name. 286 | * @return MongoCollection Returns a new collection object. 287 | * @throws Exception Throws Exception if the database or collection name is invalid. 288 | */ 289 | public function selectCollection($db, $collection) 290 | { 291 | return new MongoCollection($this->selectDB($db), $collection); 292 | } 293 | 294 | /** 295 | * Gets a database 296 | * 297 | * @link http://www.php.net/manual/en/mongo.selectdb.php 298 | * @param string $name The database name. 299 | * @return MongoDB Returns a new db object. 300 | * @throws InvalidArgumentException 301 | */ 302 | public function selectDB($name) 303 | { 304 | return new MongoDB($this, $name); 305 | } 306 | 307 | /** 308 | * {@inheritdoc} 309 | */ 310 | public function setReadPreference($readPreference, $tags = null) 311 | { 312 | return $this->setReadPreferenceFromParameters($readPreference, $tags); 313 | } 314 | 315 | /** 316 | * {@inheritdoc} 317 | */ 318 | public function setWriteConcern($wstring, $wtimeout = 0) 319 | { 320 | return $this->setWriteConcernFromParameters($wstring, $wtimeout); 321 | } 322 | 323 | /** 324 | * String representation of this connection 325 | * 326 | * @link http://www.php.net/manual/en/mongoclient.tostring.php 327 | * @return string Returns hostname and port for this connection. 328 | */ 329 | public function __toString() 330 | { 331 | return $this->server; 332 | } 333 | 334 | /** 335 | * Forces a connection by executing the ping command 336 | */ 337 | private function forceConnect() 338 | { 339 | try { 340 | $command = new \MongoDB\Driver\Command(['ping' => 1]); 341 | $this->manager->executeCommand('db', $command); 342 | } catch (\MongoDB\Driver\Exception\Exception $e) { 343 | throw ExceptionConverter::toLegacy($e); 344 | } 345 | } 346 | 347 | private function notImplemented() 348 | { 349 | throw new \Exception('Not implemented'); 350 | } 351 | 352 | /** 353 | * @return array 354 | */ 355 | public function __sleep() 356 | { 357 | return [ 358 | 'connected', 'status', 'server', 'persistent' 359 | ]; 360 | } 361 | 362 | /** 363 | * @param $server 364 | * @return array 365 | */ 366 | private function extractUrlOptions($server) 367 | { 368 | $queryOptions = parse_url($server, PHP_URL_QUERY); 369 | if (!$queryOptions) { 370 | return []; 371 | } 372 | 373 | $queryOptions = explode('&', $queryOptions); 374 | 375 | $options = []; 376 | foreach ($queryOptions as $option) { 377 | if (strpos($option, '=') === false) { 378 | continue; 379 | } 380 | 381 | $keyValue = explode('=', $option); 382 | if ($keyValue[0] === 'readPreferenceTags') { 383 | $options[$keyValue[0]][] = $this->getReadPreferenceTags($keyValue[1]); 384 | } elseif (ctype_digit($keyValue[1])) { 385 | $options[$keyValue[0]] = (int) $keyValue[1]; 386 | } else { 387 | $options[$keyValue[0]] = $keyValue[1]; 388 | } 389 | } 390 | 391 | return $options; 392 | } 393 | 394 | /** 395 | * @param $readPreferenceTagString 396 | * @return array 397 | */ 398 | private function getReadPreferenceTags($readPreferenceTagString) 399 | { 400 | $tagSets = []; 401 | foreach (explode(',', $readPreferenceTagString) as $index => $tagSet) { 402 | $tags = explode(':', $tagSet); 403 | $tagSets[$tags[0]] = $tags[1]; 404 | } 405 | 406 | return $tagSets; 407 | } 408 | 409 | /** 410 | * @param string $server 411 | * @param array $options 412 | */ 413 | private function applyConnectionOptions($server, array $options) 414 | { 415 | $urlOptions = $this->extractUrlOptions($server); 416 | 417 | if (isset($urlOptions['wTimeout'])) { 418 | $urlOptions['wTimeoutMS'] = $urlOptions['wTimeout']; 419 | unset($urlOptions['wTimeout']); 420 | } 421 | 422 | if (isset($options['wTimeout'])) { 423 | $options['wTimeoutMS'] = $options['wTimeout']; 424 | unset($options['wTimeout']); 425 | } 426 | 427 | // Special handling for readPreferenceTags which are merged 428 | if (isset($options['readPreferenceTags']) && isset($urlOptions['readPreferenceTags'])) { 429 | $options['readPreferenceTags'] = array_merge($urlOptions['readPreferenceTags'], $options['readPreferenceTags']); 430 | unset($urlOptions['readPreferenceTags']); 431 | } 432 | 433 | $urlOptions = array_merge($urlOptions, $options); 434 | 435 | if (isset($urlOptions['slaveOkay'])) { 436 | $this->setReadPreferenceFromSlaveOkay($urlOptions['slaveOkay']); 437 | } elseif (isset($urlOptions['readPreference']) || isset($urlOptions['readPreferenceTags'])) { 438 | $readPreference = isset($urlOptions['readPreference']) ? $urlOptions['readPreference'] : null; 439 | $tags = isset($urlOptions['readPreferenceTags']) ? $urlOptions['readPreferenceTags'] : null; 440 | $this->setReadPreferenceFromParameters($readPreference, $tags); 441 | } 442 | 443 | if (isset($urlOptions['w']) || isset($urlOptions['wTimeoutMs'])) { 444 | $writeConcern = (isset($urlOptions['w'])) ? $urlOptions['w'] : 1; 445 | $wTimeout = (isset($urlOptions['wTimeoutMs'])) ? $urlOptions['wTimeoutMs'] : null; 446 | $this->setWriteConcern($writeConcern, $wTimeout); 447 | } 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /lib/Mongo/MongoCursor.php: -------------------------------------------------------------------------------- 1 | query = $query; 96 | $this->projection = $fields; 97 | } 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 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * (PECL mongo >= 1.2.11)
      117 | * Sets whether this cursor will wait for a while for a tailable cursor to return more data 118 | * @param bool $wait [optional]

      If the cursor should wait for more data to become available.

      119 | * @return MongoCursor Returns this cursor. 120 | */ 121 | public function awaitData($wait = true) 122 | { 123 | $this->errorIfOpened(); 124 | $this->awaitData = $wait; 125 | 126 | return $this; 127 | } 128 | 129 | 130 | /** 131 | * Counts the number of results for this query 132 | * @link http://www.php.net/manual/en/mongocursor.count.php 133 | * @param bool $foundOnly Send cursor limit and skip information to the count function, if applicable. 134 | * @return int The number of documents returned by this cursor's query. 135 | */ 136 | #[\ReturnTypeWillChange] 137 | public function count($foundOnly = false) 138 | { 139 | $optionNames = ['hint', 'maxTimeMS']; 140 | if ($foundOnly) { 141 | $optionNames = array_merge($optionNames, ['limit', 'skip']); 142 | } 143 | 144 | $options = $this->getOptions($optionNames) + $this->options; 145 | try { 146 | $count = $this->collection->count(TypeConverter::fromLegacy($this->query), $options); 147 | } catch (\MongoDB\Driver\Exception\ExecutionTimeoutException $e) { 148 | throw new MongoCursorTimeoutException($e->getMessage(), $e->getCode(), $e); 149 | } catch (\MongoDB\Driver\Exception\Exception $e) { 150 | throw ExceptionConverter::toLegacy($e); 151 | } 152 | 153 | return $count; 154 | } 155 | 156 | /** 157 | * Execute the query 158 | * @link http://www.php.net/manual/en/mongocursor.doquery.php 159 | * @throws MongoConnectionException if it cannot reach the database. 160 | * @return void 161 | */ 162 | protected function doQuery() 163 | { 164 | $options = $this->getOptions() + $this->options; 165 | 166 | try { 167 | $this->cursor = $this->collection->find(TypeConverter::fromLegacy($this->query), $options); 168 | } catch (\MongoDB\Driver\Exception\ExecutionTimeoutException $e) { 169 | throw new MongoCursorTimeoutException($e->getMessage(), $e->getCode(), $e); 170 | } catch (\MongoDB\Driver\Exception\Exception $e) { 171 | throw ExceptionConverter::toLegacy($e); 172 | } 173 | } 174 | 175 | /** 176 | * Return an explanation of the query, often useful for optimization and debugging 177 | * @link http://www.php.net/manual/en/mongocursor.explain.php 178 | * @return array Returns an explanation of the query. 179 | */ 180 | public function explain() 181 | { 182 | $optionNames = [ 183 | 'allowPartialResults', 184 | 'batchSize', 185 | 'cursorType', 186 | 'limit', 187 | 'maxTimeMS', 188 | 'noCursorTimeout', 189 | 'projection', 190 | 'skip', 191 | 'sort', 192 | ]; 193 | 194 | $options = $this->getOptions($optionNames); 195 | 196 | $command = [ 197 | 'explain' => [ 198 | 'find' => $this->collection->getCollectionName(), 199 | 'filter' => TypeConverter::fromLegacy($this->query), 200 | ] + $options, 201 | ]; 202 | 203 | $explained = TypeConverter::toLegacy(iterator_to_array($this->db->command($command))[0]); 204 | unset($explained['ok']); 205 | 206 | return $explained; 207 | } 208 | 209 | /** 210 | * Sets the fields for a query 211 | * @link http://www.php.net/manual/en/mongocursor.fields.php 212 | * @param array $f Fields to return (or not return). 213 | * @throws MongoCursorException 214 | * @return MongoCursor 215 | */ 216 | public function fields(array $f) 217 | { 218 | $this->errorIfOpened(); 219 | $this->projection = $f; 220 | 221 | return $this; 222 | } 223 | 224 | /** 225 | * Advances the cursor to the next result, and returns that result 226 | * @link http://www.php.net/manual/en/mongocursor.getnext.php 227 | * @throws MongoConnectionException 228 | * @throws MongoCursorTimeoutException 229 | * @return array Returns the next object 230 | */ 231 | public function getNext() 232 | { 233 | return $this->next(); 234 | } 235 | 236 | /** 237 | * Checks if there are any more elements in this cursor 238 | * @link http://www.php.net/manual/en/mongocursor.hasnext.php 239 | * @throws MongoConnectionException 240 | * @throws MongoCursorTimeoutException 241 | * @return bool Returns true if there is another element 242 | */ 243 | public function hasNext() 244 | { 245 | if (! $this->startedIterating) { 246 | $this->ensureIterator(); 247 | $this->startedIterating = true; 248 | $this->storeIteratorState(); 249 | $this->cursorNeedsAdvancing = false; 250 | } elseif ($this->cursorNeedsAdvancing) { 251 | $this->ensureIterator()->next(); 252 | $this->cursorNeedsAdvancing = false; 253 | } 254 | 255 | return $this->ensureIterator()->valid(); 256 | } 257 | 258 | /** 259 | * Gives the database a hint about the query 260 | * @link http://www.php.net/manual/en/mongocursor.hint.php 261 | * @param array|string $keyPattern Indexes to use for the query. 262 | * @throws MongoCursorException 263 | * @return MongoCursor Returns this cursor 264 | */ 265 | public function hint($keyPattern) 266 | { 267 | $this->errorIfOpened(); 268 | $this->hint = $keyPattern; 269 | 270 | return $this; 271 | } 272 | 273 | /** 274 | * Sets whether this cursor will timeout 275 | * @link http://www.php.net/manual/en/mongocursor.immortal.php 276 | * @param bool $liveForever If the cursor should be immortal. 277 | * @throws MongoCursorException 278 | * @return MongoCursor Returns this cursor 279 | */ 280 | public function immortal($liveForever = true) 281 | { 282 | $this->errorIfOpened(); 283 | $this->noCursorTimeout = $liveForever; 284 | 285 | return $this; 286 | } 287 | 288 | /** 289 | * Limits the number of results returned 290 | * @link http://www.php.net/manual/en/mongocursor.limit.php 291 | * @param int $num The number of results to return. 292 | * @throws MongoCursorException 293 | * @return MongoCursor Returns this cursor 294 | */ 295 | public function limit($num) 296 | { 297 | $this->errorIfOpened(); 298 | $this->limit = $num; 299 | 300 | return $this; 301 | } 302 | 303 | /** 304 | * @param int $ms 305 | * @return $this 306 | * @throws MongoCursorException 307 | */ 308 | public function maxTimeMS($ms) 309 | { 310 | $this->errorIfOpened(); 311 | $this->maxTimeMS = $ms; 312 | 313 | return $this; 314 | } 315 | 316 | /** 317 | * @link http://www.php.net/manual/en/mongocursor.partial.php 318 | * @param bool $okay [optional]

      If receiving partial results is okay.

      319 | * @return MongoCursor Returns this cursor. 320 | */ 321 | public function partial($okay = true) 322 | { 323 | $this->allowPartialResults = $okay; 324 | 325 | return $this; 326 | } 327 | 328 | /** 329 | * Clears the cursor 330 | * @link http://www.php.net/manual/en/mongocursor.reset.php 331 | * @return void 332 | */ 333 | public function reset() 334 | { 335 | parent::reset(); 336 | } 337 | 338 | /** 339 | * @link http://www.php.net/manual/en/mongocursor.setflag.php 340 | * @param int $flag 341 | * @param bool $set 342 | * @return MongoCursor 343 | */ 344 | public function setFlag($flag, $set = true) 345 | { 346 | $this->notImplemented(); 347 | } 348 | 349 | /** 350 | * Skips a number of results 351 | * @link http://www.php.net/manual/en/mongocursor.skip.php 352 | * @param int $num The number of results to skip. 353 | * @throws MongoCursorException 354 | * @return MongoCursor Returns this cursor 355 | */ 356 | public function skip($num) 357 | { 358 | $this->errorIfOpened(); 359 | $this->skip = $num; 360 | 361 | return $this; 362 | } 363 | 364 | /** 365 | * Sets whether this query can be done on a slave 366 | * This method will override the static class variable slaveOkay. 367 | * @link http://www.php.net/manual/en/mongocursor.slaveOkay.php 368 | * @param boolean $okay If it is okay to query the slave. 369 | * @throws MongoCursorException 370 | * @return MongoCursor Returns this cursor 371 | */ 372 | public function slaveOkay($okay = true) 373 | { 374 | $this->errorIfOpened(); 375 | 376 | $this->setReadPreferenceFromSlaveOkay($okay); 377 | 378 | return $this; 379 | } 380 | 381 | /** 382 | * Use snapshot mode for the query 383 | * @link http://www.php.net/manual/en/mongocursor.snapshot.php 384 | * @throws MongoCursorException 385 | * @return MongoCursor Returns this cursor 386 | */ 387 | public function snapshot() 388 | { 389 | $this->errorIfOpened(); 390 | $this->snapshot = true; 391 | 392 | return $this; 393 | } 394 | 395 | /** 396 | * Sorts the results by given fields 397 | * @link http://www.php.net/manual/en/mongocursor.sort.php 398 | * @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 399 | * @throws MongoCursorException 400 | * @return MongoCursor Returns the same cursor that this method was called on 401 | */ 402 | public function sort(array $fields) 403 | { 404 | $this->errorIfOpened(); 405 | $this->sort = $fields; 406 | 407 | return $this; 408 | } 409 | 410 | /** 411 | * Sets whether this cursor will be left open after fetching the last results 412 | * @link http://www.php.net/manual/en/mongocursor.tailable.php 413 | * @param bool $tail If the cursor should be tailable. 414 | * @return MongoCursor Returns this cursor 415 | */ 416 | public function tailable($tail = true) 417 | { 418 | $this->errorIfOpened(); 419 | $this->tailable = $tail; 420 | 421 | return $this; 422 | } 423 | 424 | /** 425 | * @return int|null 426 | */ 427 | protected function convertCursorType() 428 | { 429 | if (! $this->tailable) { 430 | return null; 431 | } 432 | 433 | return $this->awaitData ? Find::TAILABLE_AWAIT : Find::TAILABLE; 434 | } 435 | 436 | /** 437 | * @return array 438 | */ 439 | protected function convertModifiers() 440 | { 441 | $modifiers = array_key_exists('modifiers', $this->options) ? $this->options['modifiers'] : []; 442 | 443 | foreach (['hint', 'snapshot'] as $modifier) { 444 | if ($this->$modifier === null) { 445 | continue; 446 | } 447 | 448 | $modifiers['$' . $modifier] = $this->$modifier; 449 | } 450 | 451 | return $modifiers; 452 | } 453 | 454 | /** 455 | * @return array 456 | */ 457 | protected function convertProjection() 458 | { 459 | return TypeConverter::convertProjection($this->projection); 460 | } 461 | 462 | /** 463 | * @return Cursor 464 | */ 465 | protected function ensureCursor() 466 | { 467 | if ($this->cursor === null) { 468 | $this->doQuery(); 469 | } 470 | 471 | return $this->cursor; 472 | } 473 | 474 | /** 475 | * @param \Traversable $traversable 476 | * @return CursorIterator 477 | */ 478 | protected function wrapTraversable(\Traversable $traversable) 479 | { 480 | return new CursorIterator($traversable, true); 481 | } 482 | 483 | /** 484 | * @return array 485 | */ 486 | protected function getCursorInfo() 487 | { 488 | return [ 489 | 'ns' => $this->ns, 490 | 'limit' => $this->limit, 491 | 'batchSize' => (int) $this->batchSize, 492 | 'skip' => $this->skip, 493 | 'flags' => $this->flags, 494 | 'query' => $this->query, 495 | 'fields' => $this->projection, 496 | ]; 497 | } 498 | 499 | /** 500 | * @return array 501 | */ 502 | public function __sleep() 503 | { 504 | return [ 505 | 'allowPartialResults', 506 | 'awaitData', 507 | 'flags', 508 | 'hint', 509 | 'limit', 510 | 'maxTimeMS', 511 | 'noCursorTimeout', 512 | 'optionNames', 513 | 'options', 514 | 'projection', 515 | 'query', 516 | 'skip', 517 | 'snapshot', 518 | 'sort', 519 | 'tailable', 520 | ] + parent::__sleep(); 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /lib/Mongo/MongoGridFS.php: -------------------------------------------------------------------------------- 1 | Optional collection name prefix.

      60 | * @param mixed $chunks [optional] 61 | * @throws \Exception 62 | */ 63 | public function __construct(MongoDB $db, $prefix = "fs", $chunks = null) 64 | { 65 | if ($chunks) { 66 | trigger_error("The 'chunks' argument is deprecated and ignored", E_USER_DEPRECATED); 67 | } 68 | if (empty($prefix)) { 69 | throw new \Exception('MongoGridFS::__construct(): invalid prefix'); 70 | } 71 | 72 | $this->database = $db; 73 | $this->prefix = (string) $prefix; 74 | $this->filesName = $prefix . '.files'; 75 | $this->chunksName = $prefix . '.chunks'; 76 | 77 | $this->chunks = $db->selectCollection($this->chunksName); 78 | 79 | parent::__construct($db, $this->filesName); 80 | } 81 | 82 | /** 83 | * Delete a file from the database 84 | * 85 | * @link http://php.net/manual/en/mongogridfs.delete.php 86 | * @param mixed $id _id of the file to remove 87 | * @return boolean Returns true if the remove was successfully sent to the database. 88 | */ 89 | public function delete($id) 90 | { 91 | $this->createChunksIndex(); 92 | 93 | $this->chunks->remove(['files_id' => $id], ['justOne' => false]); 94 | return parent::remove(['_id' => $id]); 95 | } 96 | 97 | /** 98 | * Drops the files and chunks collections 99 | * @link http://php.net/manual/en/mongogridfs.drop.php 100 | * @return array The database response 101 | */ 102 | public function drop() 103 | { 104 | $this->chunks->drop(); 105 | return parent::drop(); 106 | } 107 | 108 | /** 109 | * @link http://php.net/manual/en/mongogridfs.find.php 110 | * @param array $query The query 111 | * @param array $fields Fields to return 112 | * @param array $options Options for the find command 113 | * @return MongoGridFSCursor A MongoGridFSCursor 114 | */ 115 | public function find(array $query = [], array $fields = []) 116 | { 117 | $cursor = new MongoGridFSCursor($this, $this->db->getConnection(), (string) $this, $query, $fields); 118 | $cursor->setReadPreference($this->getReadPreference()); 119 | 120 | return $cursor; 121 | } 122 | 123 | /** 124 | * Returns a single file matching the criteria 125 | * 126 | * @link http://www.php.net/manual/en/mongogridfs.findone.php 127 | * @param mixed $query The fields for which to search or a filename to search for. 128 | * @param array $fields Fields of the results to return. 129 | * @param array $options Options for the find command 130 | * @return MongoGridFSFile|null 131 | */ 132 | public function findOne($query = [], array $fields = [], array $options = []) 133 | { 134 | if (! is_array($query)) { 135 | $query = ['filename' => (string) $query]; 136 | } 137 | 138 | $items = iterator_to_array($this->find($query, $fields)->limit(1)); 139 | return count($items) ? current($items) : null; 140 | } 141 | 142 | /** 143 | * Retrieve a file from the database 144 | * 145 | * @link http://www.php.net/manual/en/mongogridfs.get.php 146 | * @param mixed $id _id of the file to find. 147 | * @return MongoGridFSFile|null 148 | */ 149 | public function get($id) 150 | { 151 | return $this->findOne(['_id' => $id]); 152 | } 153 | 154 | /** 155 | * Stores a file in the database 156 | * 157 | * @link http://php.net/manual/en/mongogridfs.put.php 158 | * @param string $filename The name of the file 159 | * @param array $extra Other metadata to add to the file saved 160 | * @param array $options An array of options for the insert operations executed against the chunks and files collections. 161 | * @return mixed Returns the _id of the saved object 162 | */ 163 | public function put($filename, array $extra = [], array $options = []) 164 | { 165 | return $this->storeFile($filename, $extra, $options); 166 | } 167 | 168 | /** 169 | * Removes files from the collections 170 | * 171 | * @link http://www.php.net/manual/en/mongogridfs.remove.php 172 | * @param array $criteria Description of records to remove. 173 | * @param array $options Options for remove. 174 | * @throws MongoCursorException 175 | * @return boolean 176 | */ 177 | public function remove(array $criteria = [], array $options = []) 178 | { 179 | $this->createChunksIndex(); 180 | 181 | $matchingFiles = parent::find($criteria, ['_id' => 1]); 182 | $ids = []; 183 | foreach ($matchingFiles as $file) { 184 | $ids[] = $file['_id']; 185 | } 186 | $this->chunks->remove(['files_id' => ['$in' => $ids]], ['justOne' => false] + $options); 187 | return parent::remove(['_id' => ['$in' => $ids]], ['justOne' => false] + $options); 188 | } 189 | 190 | /** 191 | * Chunkifies and stores bytes in the database 192 | * @link http://php.net/manual/en/mongogridfs.storebytes.php 193 | * @param string $bytes A string of bytes to store 194 | * @param array $extra Other metadata to add to the file saved 195 | * @param array $options Options for the store. "safe": Check that this store succeeded 196 | * @return mixed The _id of the object saved 197 | */ 198 | public function storeBytes($bytes, array $extra = [], array $options = []) 199 | { 200 | $this->createChunksIndex(); 201 | 202 | $record = $extra + [ 203 | 'length' => mb_strlen($bytes, '8bit'), 204 | 'md5' => md5($bytes), 205 | ]; 206 | 207 | try { 208 | $file = $this->insertFile($record, $options); 209 | } catch (MongoException $e) { 210 | throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e); 211 | } 212 | 213 | try { 214 | $this->insertChunksFromBytes($bytes, $file); 215 | } catch (MongoException $e) { 216 | $this->delete($file['_id']); 217 | throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e); 218 | } 219 | 220 | return $file['_id']; 221 | } 222 | 223 | /** 224 | * Stores a file in the database 225 | * 226 | * @link http://php.net/manual/en/mongogridfs.storefile.php 227 | * @param string $filename The name of the file 228 | * @param array $extra Other metadata to add to the file saved 229 | * @param array $options Options for the store. "safe": Check that this store succeeded 230 | * @return mixed Returns the _id of the saved object 231 | * @throws MongoGridFSException 232 | * @throws Exception 233 | */ 234 | public function storeFile($filename, array $extra = [], array $options = []) 235 | { 236 | $this->createChunksIndex(); 237 | 238 | $record = $extra; 239 | if (is_string($filename)) { 240 | $record += [ 241 | 'md5' => md5_file($filename), 242 | 'length' => filesize($filename), 243 | 'filename' => $filename, 244 | ]; 245 | 246 | $handle = fopen($filename, 'r'); 247 | if (! $handle) { 248 | throw new MongoGridFSException('could not open file: ' . $filename); 249 | } 250 | } elseif (! is_resource($filename)) { 251 | throw new \Exception('first argument must be a string or stream resource'); 252 | } else { 253 | $handle = $filename; 254 | } 255 | 256 | $md5 = null; 257 | try { 258 | $file = $this->insertFile($record, $options); 259 | } catch (MongoException $e) { 260 | throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e); 261 | } 262 | 263 | try { 264 | $length = $this->insertChunksFromFile($handle, $file, $md5); 265 | } catch (MongoException $e) { 266 | $this->delete($file['_id']); 267 | throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e); 268 | } 269 | 270 | 271 | // Add length and MD5 if they were not present before 272 | $update = []; 273 | if (! isset($record['length'])) { 274 | $update['length'] = $length; 275 | } 276 | if (! isset($record['md5'])) { 277 | try { 278 | $update['md5'] = $md5; 279 | } catch (MongoException $e) { 280 | throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e); 281 | } 282 | } 283 | 284 | if (count($update)) { 285 | try { 286 | $result = $this->update(['_id' => $file['_id']], ['$set' => $update]); 287 | if (! $this->isOKResult($result)) { 288 | throw new MongoGridFSException('Could not store file'); 289 | } 290 | } catch (MongoException $e) { 291 | $this->delete($file['_id']); 292 | throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e); 293 | } 294 | } 295 | 296 | return $file['_id']; 297 | } 298 | 299 | /** 300 | * Saves an uploaded file directly from a POST to the database 301 | * 302 | * @link http://www.php.net/manual/en/mongogridfs.storeupload.php 303 | * @param string $name The name attribute of the uploaded file, from . 304 | * @param array $metadata An array of extra fields for the uploaded file. 305 | * @return mixed Returns the _id of the uploaded file. 306 | * @throws MongoGridFSException 307 | */ 308 | public function storeUpload($name, array $metadata = []) 309 | { 310 | if (! isset($_FILES[$name]) || $_FILES[$name]['error'] !== UPLOAD_ERR_OK) { 311 | throw new MongoGridFSException("Could not find uploaded file $name"); 312 | } 313 | if (! isset($_FILES[$name]['tmp_name'])) { 314 | throw new MongoGridFSException("Couldn't find tmp_name in the \$_FILES array. Are you sure the upload worked?"); 315 | } 316 | 317 | $uploadedFile = $_FILES[$name]; 318 | $uploadedFile['tmp_name'] = (array) $uploadedFile['tmp_name']; 319 | $uploadedFile['name'] = (array) $uploadedFile['name']; 320 | 321 | if (count($uploadedFile['tmp_name']) > 1) { 322 | foreach ($uploadedFile['tmp_name'] as $key => $file) { 323 | $metadata['filename'] = $uploadedFile['name'][$key]; 324 | $this->storeFile($file, $metadata); 325 | } 326 | 327 | return null; 328 | } else { 329 | $metadata += ['filename' => array_pop($uploadedFile['name'])]; 330 | return $this->storeFile(array_pop($uploadedFile['tmp_name']), $metadata); 331 | } 332 | } 333 | 334 | /** 335 | * Creates the index on the chunks collection 336 | */ 337 | private function createChunksIndex() 338 | { 339 | try { 340 | $this->chunks->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]); 341 | } catch (MongoDuplicateKeyException $e) { 342 | } 343 | } 344 | 345 | /** 346 | * Inserts a single chunk into the database 347 | * 348 | * @param mixed $fileId 349 | * @param string $data 350 | * @param int $chunkNumber 351 | * @return array|bool 352 | */ 353 | private function insertChunk($fileId, $data, $chunkNumber) 354 | { 355 | $chunk = [ 356 | 'files_id' => $fileId, 357 | 'n' => $chunkNumber, 358 | 'data' => new MongoBinData($data), 359 | ]; 360 | 361 | $result = $this->chunks->insert($chunk); 362 | 363 | if (! $this->isOKResult($result)) { 364 | throw new \MongoException('error inserting chunk'); 365 | } 366 | 367 | return $result; 368 | } 369 | 370 | /** 371 | * Splits a string into chunks and writes them to the database 372 | * 373 | * @param string $bytes 374 | * @param array $record 375 | */ 376 | private function insertChunksFromBytes($bytes, $record) 377 | { 378 | $chunkSize = $record['chunkSize']; 379 | $fileId = $record['_id']; 380 | $i = 0; 381 | 382 | $chunks = str_split($bytes, $chunkSize); 383 | foreach ($chunks as $chunk) { 384 | $this->insertChunk($fileId, $chunk, $i++); 385 | } 386 | } 387 | 388 | /** 389 | * Reads chunks from a file and writes them to the database 390 | * 391 | * @param resource $handle 392 | * @param array $record 393 | * @param string $md5 394 | * @return int Returns the number of bytes written to the database 395 | */ 396 | private function insertChunksFromFile($handle, $record, &$md5) 397 | { 398 | $written = 0; 399 | $offset = 0; 400 | $i = 0; 401 | 402 | $fileId = $record['_id']; 403 | $chunkSize = $record['chunkSize']; 404 | 405 | $hash = hash_init('md5'); 406 | 407 | rewind($handle); 408 | while (! feof($handle)) { 409 | $data = stream_get_contents($handle, $chunkSize); 410 | hash_update($hash, $data); 411 | $this->insertChunk($fileId, $data, $i++); 412 | $written += strlen($data); 413 | $offset += $chunkSize; 414 | } 415 | 416 | $md5 = hash_final($hash); 417 | 418 | return $written; 419 | } 420 | 421 | /** 422 | * Writes a file record to the database 423 | * 424 | * @param $record 425 | * @param array $options 426 | * @return array 427 | */ 428 | private function insertFile($record, array $options = []) 429 | { 430 | $record += [ 431 | '_id' => new MongoId(), 432 | 'uploadDate' => new MongoDate(), 433 | 'chunkSize' => $this->defaultChunkSize, 434 | ]; 435 | 436 | $result = $this->insert($record, $options); 437 | 438 | if (! $this->isOKResult($result)) { 439 | throw new \MongoException('error inserting file'); 440 | } 441 | 442 | return $record; 443 | } 444 | 445 | private function isOKResult($result) 446 | { 447 | return (is_array($result) && $result['ok'] == 1.0) || 448 | (is_bool($result) && $result); 449 | } 450 | 451 | /** 452 | * @return array 453 | */ 454 | public function __sleep() 455 | { 456 | return ['chunks', 'chunksName', 'database', 'defaultChunkSize', 'filesName', 'prefix'] + parent::__sleep(); 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /lib/Mongo/MongoDB.php: -------------------------------------------------------------------------------- 1 | checkDatabaseName($name); 66 | $this->connection = $conn; 67 | $this->name = (string) $name; 68 | 69 | $this->setReadPreferenceFromArray($conn->getReadPreference()); 70 | $this->setWriteConcernFromArray($conn->getWriteConcern()); 71 | 72 | $this->createDatabaseObject(); 73 | } 74 | 75 | /** 76 | * @return \MongoDB\Database 77 | * @internal This method is not part of the ext-mongo API 78 | */ 79 | public function getDb() 80 | { 81 | return $this->db; 82 | } 83 | 84 | /** 85 | * The name of this database 86 | * 87 | * @link http://www.php.net/manual/en/mongodb.--tostring.php 88 | * @return string Returns this database's name. 89 | */ 90 | public function __toString() 91 | { 92 | return $this->name; 93 | } 94 | 95 | /** 96 | * Gets a collection 97 | * 98 | * @link http://www.php.net/manual/en/mongodb.get.php 99 | * @param string $name The name of the collection. 100 | * @return MongoCollection 101 | */ 102 | public function __get($name) 103 | { 104 | // Handle w and wtimeout properties that replicate data stored in $readPreference 105 | if ($name === 'w' || $name === 'wtimeout') { 106 | return $this->getWriteConcern()[$name]; 107 | } 108 | 109 | return $this->selectCollection($name); 110 | } 111 | 112 | /** 113 | * @param string $name 114 | * @param mixed $value 115 | */ 116 | public function __set($name, $value) 117 | { 118 | if ($name === 'w' || $name === 'wtimeout') { 119 | trigger_error("The '{$name}' property is read-only", E_USER_DEPRECATED); 120 | } 121 | } 122 | 123 | /** 124 | * Returns information about collections in this database 125 | * 126 | * @link http://www.php.net/manual/en/mongodb.getcollectioninfo.php 127 | * @param array $options An array of options for listing the collections. 128 | * @return array 129 | */ 130 | public function getCollectionInfo(array $options = []) 131 | { 132 | $includeSystemCollections = false; 133 | // The includeSystemCollections option is no longer supported in the command 134 | if (isset($options['includeSystemCollections'])) { 135 | $includeSystemCollections = $options['includeSystemCollections']; 136 | unset($options['includeSystemCollections']); 137 | } 138 | 139 | try { 140 | $collections = $this->db->listCollections($options); 141 | } catch (\MongoDB\Driver\Exception\Exception $e) { 142 | throw ExceptionConverter::toLegacy($e); 143 | } 144 | 145 | $getCollectionInfo = function (CollectionInfo $collectionInfo) { 146 | // @todo do away with __debugInfo once https://jira.mongodb.org/browse/PHPLIB-226 is fixed 147 | $info = $collectionInfo->__debugInfo(); 148 | 149 | return array_filter( 150 | [ 151 | 'name' => $collectionInfo->getName(), 152 | 'type' => isset($info['type']) ? $info['type'] : null, 153 | 'options' => $collectionInfo->getOptions(), 154 | 'info' => isset($info['info']) ? (array) $info['info'] : null, 155 | 'idIndex' => isset($info['idIndex']) ? (array) $info['idIndex'] : null, 156 | ], 157 | function ($item) { 158 | return $item !== null; 159 | } 160 | ); 161 | }; 162 | 163 | $eligibleCollections = array_filter( 164 | iterator_to_array($collections), 165 | $this->getSystemCollectionFilterClosure($includeSystemCollections) 166 | ); 167 | 168 | return array_map($getCollectionInfo, $eligibleCollections); 169 | } 170 | 171 | /** 172 | * Get all collections from this database 173 | * 174 | * @link http://www.php.net/manual/en/mongodb.getcollectionnames.php 175 | * @param array $options An array of options for listing the collections. 176 | * @return array Returns the names of the all the collections in the database as an array 177 | */ 178 | public function getCollectionNames(array $options = []) 179 | { 180 | $includeSystemCollections = false; 181 | // The includeSystemCollections option is no longer supported in the command 182 | if (isset($options['includeSystemCollections'])) { 183 | $includeSystemCollections = $options['includeSystemCollections']; 184 | unset($options['includeSystemCollections']); 185 | } 186 | 187 | try { 188 | $collections = $this->db->listCollections($options); 189 | } catch (\MongoDB\Driver\Exception\Exception $e) { 190 | throw ExceptionConverter::toLegacy($e); 191 | } 192 | 193 | $getCollectionName = function (CollectionInfo $collectionInfo) { 194 | return $collectionInfo->getName(); 195 | }; 196 | 197 | $eligibleCollections = array_filter( 198 | iterator_to_array($collections), 199 | $this->getSystemCollectionFilterClosure($includeSystemCollections) 200 | ); 201 | 202 | return array_map($getCollectionName, $eligibleCollections); 203 | } 204 | 205 | /** 206 | * @return MongoClient 207 | * @internal This method is not part of the ext-mongo API 208 | */ 209 | public function getConnection() 210 | { 211 | return $this->connection; 212 | } 213 | 214 | /** 215 | * Fetches toolkit for dealing with files stored in this database 216 | * 217 | * @link http://www.php.net/manual/en/mongodb.getgridfs.php 218 | * @param string $prefix The prefix for the files and chunks collections. 219 | * @return MongoGridFS Returns a new gridfs object for this database. 220 | */ 221 | public function getGridFS($prefix = "fs") 222 | { 223 | return new \MongoGridFS($this, $prefix); 224 | } 225 | 226 | /** 227 | * Gets this database's profiling level 228 | * 229 | * @link http://www.php.net/manual/en/mongodb.getprofilinglevel.php 230 | * @return int Returns the profiling level. 231 | */ 232 | public function getProfilingLevel() 233 | { 234 | $result = $this->command(['profile' => -1]); 235 | 236 | return ($result['ok'] && isset($result['was'])) ? $result['was'] : 0; 237 | } 238 | 239 | /** 240 | * Sets this database's profiling level 241 | * 242 | * @link http://www.php.net/manual/en/mongodb.setprofilinglevel.php 243 | * @param int $level Profiling level. 244 | * @return int Returns the previous profiling level. 245 | */ 246 | public function setProfilingLevel($level) 247 | { 248 | $result = $this->command(['profile' => $level]); 249 | 250 | return ($result['ok'] && isset($result['was'])) ? $result['was'] : 0; 251 | } 252 | 253 | /** 254 | * Drops this database 255 | * 256 | * @link http://www.php.net/manual/en/mongodb.drop.php 257 | * @return array Returns the database response. 258 | */ 259 | public function drop() 260 | { 261 | return TypeConverter::toLegacy($this->db->drop()); 262 | } 263 | 264 | /** 265 | * Repairs and compacts this database 266 | * 267 | * @link http://www.php.net/manual/en/mongodb.repair.php 268 | * @param bool $preserve_cloned_files [optional]

      If cloned files should be kept if the repair fails.

      269 | * @param bool $backup_original_files [optional]

      If original files should be backed up.

      270 | * @return array

      Returns db response.

      271 | */ 272 | public function repair($preserve_cloned_files = false, $backup_original_files = false) 273 | { 274 | $command = [ 275 | 'repairDatabase' => 1, 276 | 'preserveClonedFilesOnFailure' => $preserve_cloned_files, 277 | 'backupOriginalFiles' => $backup_original_files, 278 | ]; 279 | 280 | return $this->command($command); 281 | } 282 | 283 | /** 284 | * Gets a collection 285 | * 286 | * @link http://www.php.net/manual/en/mongodb.selectcollection.php 287 | * @param string $name The collection name. 288 | * @throws Exception if the collection name is invalid. 289 | * @return MongoCollection Returns a new collection object. 290 | */ 291 | public function selectCollection($name) 292 | { 293 | return new MongoCollection($this, $name); 294 | } 295 | 296 | /** 297 | * Creates a collection 298 | * 299 | * @link http://www.php.net/manual/en/mongodb.createcollection.php 300 | * @param string $name The name of the collection. 301 | * @param array $options 302 | * @return MongoCollection Returns a collection object representing the new collection. 303 | */ 304 | public function createCollection($name, $options = []) 305 | { 306 | try { 307 | if (isset($options['capped'])) { 308 | $options['capped'] = (bool) $options['capped']; 309 | } 310 | 311 | $this->db->createCollection($name, $options); 312 | } catch (\MongoDB\Driver\Exception\Exception $e) { 313 | return false; 314 | } 315 | 316 | return $this->selectCollection($name); 317 | } 318 | 319 | /** 320 | * Drops a collection 321 | * 322 | * @link http://www.php.net/manual/en/mongodb.dropcollection.php 323 | * @param MongoCollection|string $coll MongoCollection or name of collection to drop. 324 | * @return array Returns the database response. 325 | * 326 | * @deprecated Use MongoCollection::drop() instead. 327 | */ 328 | public function dropCollection($coll) 329 | { 330 | if ($coll instanceof MongoCollection) { 331 | $coll = $coll->getName(); 332 | } 333 | 334 | return TypeConverter::toLegacy($this->db->dropCollection((string) $coll)); 335 | } 336 | 337 | /** 338 | * Get a list of collections in this database 339 | * 340 | * @link http://www.php.net/manual/en/mongodb.listcollections.php 341 | * @param array $options 342 | * @return MongoCollection[] Returns a list of MongoCollections. 343 | */ 344 | public function listCollections(array $options = []) 345 | { 346 | return array_map([$this, 'selectCollection'], $this->getCollectionNames($options)); 347 | } 348 | 349 | /** 350 | * Creates a database reference 351 | * 352 | * @link http://www.php.net/manual/en/mongodb.createdbref.php 353 | * @param string $collection The collection to which the database reference will point. 354 | * @param mixed $document_or_id 355 | * @return array Returns a database reference array. 356 | */ 357 | public function createDBRef($collection, $document_or_id) 358 | { 359 | if ($document_or_id instanceof \MongoId) { 360 | $id = $document_or_id; 361 | } elseif (is_object($document_or_id)) { 362 | if (! isset($document_or_id->_id)) { 363 | $id = $document_or_id; 364 | } else { 365 | $id = $document_or_id->_id; 366 | } 367 | } elseif (is_array($document_or_id)) { 368 | if (! isset($document_or_id['_id'])) { 369 | return null; 370 | } 371 | 372 | $id = $document_or_id['_id']; 373 | } else { 374 | $id = $document_or_id; 375 | } 376 | 377 | return MongoDBRef::create($collection, $id); 378 | } 379 | 380 | 381 | /** 382 | * Fetches the document pointed to by a database reference 383 | * 384 | * @link http://www.php.net/manual/en/mongodb.getdbref.php 385 | * @param array $ref A database reference. 386 | * @return array Returns the document pointed to by the reference. 387 | */ 388 | public function getDBRef(array $ref) 389 | { 390 | $db = (isset($ref['$db']) && $ref['$db'] !== $this->name) ? $this->connection->selectDB($ref['$db']) : $this; 391 | return MongoDBRef::get($db, $ref); 392 | } 393 | 394 | /** 395 | * Runs JavaScript code on the database server. 396 | * 397 | * @link http://www.php.net/manual/en/mongodb.execute.php 398 | * @param MongoCode|string $code Code to execute. 399 | * @param array $args [optional] Arguments to be passed to code. 400 | * @return array Returns the result of the evaluation. 401 | */ 402 | public function execute($code, array $args = []) 403 | { 404 | return $this->command(['eval' => $code, 'args' => $args]); 405 | } 406 | 407 | /** 408 | * Execute a database command 409 | * 410 | * @link http://www.php.net/manual/en/mongodb.command.php 411 | * @param array $data The query to send. 412 | * @param array $options 413 | * @return array Returns database response. 414 | */ 415 | public function command(array $data, $options = [], &$hash = null) 416 | { 417 | try { 418 | $cursor = new \MongoCommandCursor($this->connection, $this->name, $data); 419 | $cursor->setReadPreference($this->getReadPreference()); 420 | 421 | return iterator_to_array($cursor)[0]; 422 | } catch (\MongoDB\Driver\Exception\Exception $e) { 423 | return ExceptionConverter::toResultArray($e); 424 | } 425 | } 426 | 427 | /** 428 | * Check if there was an error on the most recent db operation performed 429 | * 430 | * @link http://www.php.net/manual/en/mongodb.lasterror.php 431 | * @return array Returns the error, if there was one. 432 | */ 433 | public function lastError() 434 | { 435 | return $this->command(array('getLastError' => 1)); 436 | } 437 | 438 | /** 439 | * Checks for the last error thrown during a database operation 440 | * 441 | * @link http://www.php.net/manual/en/mongodb.preverror.php 442 | * @return array Returns the error and the number of operations ago it occurred. 443 | */ 444 | public function prevError() 445 | { 446 | return $this->command(array('getPrevError' => 1)); 447 | } 448 | 449 | /** 450 | * Clears any flagged errors on the database 451 | * 452 | * @link http://www.php.net/manual/en/mongodb.reseterror.php 453 | * @return array Returns the database response. 454 | */ 455 | public function resetError() 456 | { 457 | return $this->command(array('resetError' => 1)); 458 | } 459 | 460 | /** 461 | * Creates a database error 462 | * 463 | * @link http://www.php.net/manual/en/mongodb.forceerror.php 464 | * @return boolean Returns the database response. 465 | */ 466 | public function forceError() 467 | { 468 | return $this->command(array('forceerror' => 1)); 469 | } 470 | 471 | /** 472 | * Log in to this database 473 | * 474 | * @link http://www.php.net/manual/en/mongodb.authenticate.php 475 | * @param string $username The username. 476 | * @param string $password The password (in plaintext). 477 | * @return array Returns database response. If the login was successful, it will return 1. 478 | * 479 | * @deprecated This method is not implemented, supply authentication credentials through the connection string instead. 480 | */ 481 | public function authenticate($username, $password) 482 | { 483 | throw new \Exception('The MongoDB::authenticate method is not supported. Please supply authentication credentials through the connection string'); 484 | } 485 | 486 | /** 487 | * {@inheritdoc} 488 | */ 489 | public function setReadPreference($readPreference, $tags = null) 490 | { 491 | $result = $this->setReadPreferenceFromParameters($readPreference, $tags); 492 | $this->createDatabaseObject(); 493 | 494 | return $result; 495 | } 496 | 497 | /** 498 | * {@inheritdoc} 499 | */ 500 | public function setWriteConcern($wstring, $wtimeout = 0) 501 | { 502 | $result = $this->setWriteConcernFromParameters($wstring, $wtimeout); 503 | $this->createDatabaseObject(); 504 | 505 | return $result; 506 | } 507 | 508 | protected function notImplemented() 509 | { 510 | throw new \Exception('Not implemented'); 511 | } 512 | 513 | /** 514 | * @return \MongoDB\Database 515 | */ 516 | private function createDatabaseObject() 517 | { 518 | $options = [ 519 | 'readPreference' => $this->readPreference, 520 | 'writeConcern' => $this->writeConcern, 521 | ]; 522 | 523 | if ($this->db === null) { 524 | $this->db = $this->connection->getClient()->selectDatabase($this->name, $options); 525 | } else { 526 | $this->db = $this->db->withOptions($options); 527 | } 528 | } 529 | 530 | private function checkDatabaseName($name) 531 | { 532 | if (empty($name)) { 533 | throw new \Exception('Database name cannot be empty'); 534 | } 535 | if (strlen($name) >= 64) { 536 | throw new \Exception('Database name cannot exceed 63 characters'); 537 | } 538 | if (strpos($name, chr(0)) !== false) { 539 | throw new \Exception('Database name cannot contain null bytes'); 540 | } 541 | 542 | $invalidCharacters = ['.', '$', '/', ' ', '\\']; 543 | foreach ($invalidCharacters as $char) { 544 | if (strchr($name, $char) !== false) { 545 | throw new \Exception('Database name contains invalid characters'); 546 | } 547 | } 548 | } 549 | 550 | /** 551 | * @param bool $includeSystemCollections 552 | * @return Closure 553 | */ 554 | private function getSystemCollectionFilterClosure($includeSystemCollections = false) 555 | { 556 | return function (CollectionInfo $collectionInfo) use ($includeSystemCollections) { 557 | return $includeSystemCollections || ! preg_match('#^system\.#', $collectionInfo->getName()); 558 | }; 559 | } 560 | 561 | /** 562 | * @return array 563 | */ 564 | public function __sleep() 565 | { 566 | return ['connection', 'name']; 567 | } 568 | } 569 | --------------------------------------------------------------------------------