├── 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 arrayThe 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 arrayA 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 | :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_function101 | * 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 | *One of the {@link http://www.php.net/manual/en/class.mongolog.php#mongolog.constants.module MongoLog module constants}.
113 | *One of the {@link http://www.php.net/manual/en/class.mongolog.php#mongolog.constants.level MongoLog level constants}.
118 | *The log message itself.
123 | *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 $sizeThe 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)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 arrayReturns 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 | --------------------------------------------------------------------------------