From db029e1c0657b1035b905ec56ce7594f104e3670 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 23 Jan 2020 23:41:29 +0330 Subject: [PATCH 01/82] yii2 mongodb transaction --- src/ClientSession.php | 86 ++++++++++++++++ src/Collection.php | 98 ++++++++++-------- src/Command.php | 233 ++++++++++++++++-------------------------- src/Connection.php | 198 +++++++++++++++++++++++++++++++++++ src/Database.php | 15 +-- src/Transaction.php | 85 +++++++++++++++ 6 files changed, 522 insertions(+), 193 deletions(-) create mode 100644 src/ClientSession.php create mode 100644 src/Transaction.php diff --git a/src/ClientSession.php b/src/ClientSession.php new file mode 100644 index 000000000..347de8e17 --- /dev/null +++ b/src/ClientSession.php @@ -0,0 +1,86 @@ + + */ +class ClientSession extends \yii\base\BaseObject +{ + + /** + * @var Connection the database connection that this transaction is associated with. + */ + public $db; + + /** + * @var MongoDB\Driver\Session class represents a client session and Commands, + * queries, and write operations may then be associated the session. + * @see https://www.php.net/manual/en/class.mongodb-driver-session.php + */ + public $mongoSession; + + /** + * @var Transaction current transaction in session. this transaction can only be created once. + */ + private $_transaction = null; + + /** + * Start a new session in a connection. + * @param Connection $db + * @param Array $sessionOptions Creates a ClientSession for the given options + * @see https://www.php.net/manual/en/mongodb-driver-manager.startsession.php#refsect1-mongodb-driver-manager.startsession-parameters + * @return ClientSession return new session base on a session options for the given connection + */ + public static function start($db, $sessionOptions = []){ + Connection::prepareExecOptions($sessionOptions); + Yii::trace('Starting mongodb session ...', __METHOD__); + $db->trigger(Connection::EVENT_START_SESSION); + $newSession = new self([ + 'db' => $db, + 'mongoSession' => $db->manager->startSession($sessionOptions), + ]); + Yii::trace('MongoDB session started.', __METHOD__); + return $newSession; + } + + /** + * Get current transaction of session or create a new transaction once + * @return Transaction return current transaction + */ + public function getTransaction(){ + if($this->_transaction === null) + return $this->_transaction = new Transaction(['clientSession' => $this]); + return $this->_transaction; + } + + /** + * current session has a transaction? + * @return bool return true if transaction exists otherwise return false + */ + public function getHasTransaction(){ + return !empty($this->_transaction); + } + + /** + * End current session + */ + public function end(){ + $this->mongoSession->endSession(); + $db->trigger(Connection::EVENT_END_SESSION); + } +} diff --git a/src/Collection.php b/src/Collection.php index c897fd4cb..f6ea975c6 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -56,23 +56,25 @@ public function getFullName() /** * Drops this collection. + * @param array $execOptions -> goto Command::execute() * @throws Exception on failure. * @return bool whether the operation successful. */ - public function drop() + public function drop($execOptions = []) { - return $this->database->dropCollection($this->name); + return $this->database->dropCollection($this->name, $execOptions); } /** * Returns the list of defined indexes. * @return array list of indexes info. * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::execute() * @since 2.1 */ - public function listIndexes($options = []) + public function listIndexes($options = [], $execOptions = []) { - return $this->database->createCommand()->listIndexes($this->name, $options); + return $this->database->createCommand()->listIndexes($this->name, $options, $execOptions); } /** @@ -107,23 +109,25 @@ public function listIndexes($options = []) * * See [[https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#options-for-all-index-types]] * for the full list of options. + * @param array $execOptions -> goto Command::execute() * @return bool whether operation was successful. * @since 2.1 */ - public function createIndexes($indexes) + public function createIndexes($indexes, $execOptions = []) { - return $this->database->createCommand()->createIndexes($this->name, $indexes); + return $this->database->createCommand()->createIndexes($this->name, $indexes, $execOptions); } /** * Drops collection indexes by name. * @param string $indexes wildcard for name of the indexes to be dropped. * You can use `*` to drop all indexes. + * @param array $execOptions -> goto Command::execute() * @return int count of dropped indexes. */ - public function dropIndexes($indexes) + public function dropIndexes($indexes, $execOptions = []) { - $result = $this->database->createCommand()->dropIndexes($this->name, $indexes); + $result = $this->database->createCommand()->dropIndexes($this->name, $indexes, $execOptions); return $result['nIndexesWas']; } @@ -144,13 +148,14 @@ public function dropIndexes($indexes) * ``` * * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::execute() * @throws Exception on failure. * @return bool whether the operation successful. */ - public function createIndex($columns, $options = []) + public function createIndex($columns, $options = [], $execOptions = []) { $index = array_merge(['key' => $columns], $options); - return $this->database->createCommand()->createIndexes($this->name, [$index]); + return $this->database->createCommand()->createIndexes($this->name, [$index], $execOptions); } /** @@ -171,17 +176,18 @@ public function createIndex($columns, $options = []) * ] * ``` * + * @param array $execOptions -> goto Command::execute() * @throws Exception on failure. * @return bool whether the operation successful. */ - public function dropIndex($columns) + public function dropIndex($columns, $execOptions = []) { $existingIndexes = $this->listIndexes(); $indexKey = $this->database->connection->getQueryBuilder()->buildSortFields($columns); foreach ($existingIndexes as $index) { if ($index['key'] == $indexKey) { - $this->database->createCommand()->dropIndexes($this->name, $index['name']); + $this->database->createCommand()->dropIndexes($this->name, $index['name'], $execOptions); return true; } } @@ -190,7 +196,7 @@ public function dropIndex($columns) $indexName = $this->database->connection->getQueryBuilder()->generateIndexName($indexKey); foreach ($existingIndexes as $index) { if ($index['name'] === $indexName) { - $this->database->createCommand()->dropIndexes($this->name, $index['name']); + $this->database->createCommand()->dropIndexes($this->name, $index['name'], $execOptions); return true; } } @@ -200,12 +206,13 @@ public function dropIndex($columns) /** * Drops all indexes for this collection. + * @param array $execOptions -> goto Command::execute() * @throws Exception on failure. * @return int count of dropped indexes. */ - public function dropAllIndexes() + public function dropAllIndexes($execOptions = []) { - $result = $this->database->createCommand()->dropIndexes($this->name, '*'); + $result = $this->database->createCommand()->dropIndexes($this->name, '*', $execOptions); return isset($result['nIndexesWas']) ? $result['nIndexesWas'] : 0; } @@ -215,15 +222,16 @@ public function dropAllIndexes() * @param array $condition query condition * @param array $fields fields to be selected * @param array $options query options (available since 2.1). + * @param array $execOptions -> goto Command::executeQuery() * @return \MongoDB\Driver\Cursor cursor for the search results * @see Query */ - public function find($condition = [], $fields = [], $options = []) + public function find($condition = [], $fields = [], $options = [], $execOptions = []) { if (!empty($fields)) { $options['projection'] = $fields; } - return $this->database->createCommand()->find($this->name, $condition, $options); + return $this->database->createCommand()->find($this->name, $condition, $options, $execOptions); } /** @@ -231,12 +239,13 @@ public function find($condition = [], $fields = [], $options = []) * @param array $condition query condition * @param array $fields fields to be selected * @param array $options query options (available since 2.1). + * @param array $execOptions -> goto Command::executeQuery() * @return array|null the single document. Null is returned if the query results in nothing. */ - public function findOne($condition = [], $fields = [], $options = []) + public function findOne($condition = [], $fields = [], $options = [], $execOptions = []) { $options['limit'] = 1; - $cursor = $this->find($condition, $fields, $options); + $cursor = $this->find($condition, $fields, $options, $execOptions); $rows = $cursor->toArray(); return empty($rows) ? null : current($rows); } @@ -249,9 +258,9 @@ public function findOne($condition = [], $fields = [], $options = []) * @return array|null the original document, or the modified document when $options['new'] is set. * @throws Exception on failure. */ - public function findAndModify($condition, $update, $options = []) + public function findAndModify($condition, $update, $options = [], $execOptions = []) { - return $this->database->createCommand()->findAndModify($this->name, $condition, $update, $options); + return $this->database->createCommand()->findAndModify($this->name, $condition, $update, $options, $execOptions); } /** @@ -259,23 +268,25 @@ public function findAndModify($condition, $update, $options = []) * @param array|object $data data to be inserted. * @param array $options list of options in format: optionName => optionValue. * @return \MongoDB\BSON\ObjectID new record ID instance. + * @param array $execOptions -> goto Command::executeBatch() * @throws Exception on failure. */ - public function insert($data, $options = []) + public function insert($data, $options = [], $execOptions = []) { - return $this->database->createCommand()->insert($this->name, $data, $options); + return $this->database->createCommand()->insert($this->name, $data, $options, $execOptions); } /** * Inserts several new rows into collection. * @param array $rows array of arrays or objects to be inserted. * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::executeBatch() * @return array inserted data, each row will have "_id" key assigned to it. * @throws Exception on failure. */ - public function batchInsert($rows, $options = []) + public function batchInsert($rows, $options = [], $execOptions = []) { - $insertedIds = $this->database->createCommand()->batchInsert($this->name, $rows, $options); + $insertedIds = $this->database->createCommand()->batchInsert($this->name, $rows, $options, $execOptions); foreach ($rows as $key => $row) { $rows[$key]['_id'] = $insertedIds[$key]; } @@ -289,12 +300,13 @@ public function batchInsert($rows, $options = []) * @param array $condition description of the objects to update. * @param array $newData the object with which to update the matching records. * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::executeBatch() * @return int|bool number of updated documents or whether operation was successful. * @throws Exception on failure. */ - public function update($condition, $newData, $options = []) + public function update($condition, $newData, $options = [], $execOptions = []) { - $writeResult = $this->database->createCommand()->update($this->name, $condition, $newData, $options); + $writeResult = $this->database->createCommand()->update($this->name, $condition, $newData, $options, $execOptions); return $writeResult->getModifiedCount() + $writeResult->getUpsertedCount(); } @@ -303,16 +315,17 @@ public function update($condition, $newData, $options = []) * @param array|object $data data to be updated/inserted. * @param array $options list of options in format: optionName => optionValue. * @return \MongoDB\BSON\ObjectID updated/new record id instance. + * @param array $execOptions -> goto Command::executeBatch() * @throws Exception on failure. */ - public function save($data, $options = []) + public function save($data, $options = [], $execOptions = []) { if (empty($data['_id'])) { - return $this->insert($data, $options); + return $this->insert($data, $options, $execOptions); } $id = $data['_id']; unset($data['_id']); - $this->update(['_id' => $id], ['$set' => $data], ['upsert' => true]); + $this->update(['_id' => $id], ['$set' => $data], ['upsert' => true], $execOptions); return is_object($id) ? $id : new ObjectID($id); } @@ -321,13 +334,14 @@ public function save($data, $options = []) * Removes data from the collection. * @param array $condition description of records to remove. * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::executeBatch() * @return int|bool number of updated documents or whether operation was successful. * @throws Exception on failure. */ - public function remove($condition = [], $options = []) + public function remove($condition = [], $options = [], $execOptions = []) { $options = array_merge(['limit' => 0], $options); - $writeResult = $this->database->createCommand()->delete($this->name, $condition, $options); + $writeResult = $this->database->createCommand()->delete($this->name, $condition, $options, $execOptions); return $writeResult->getDeletedCount(); } @@ -338,9 +352,9 @@ public function remove($condition = [], $options = []) * @return int records count. * @since 2.1 */ - public function count($condition = [], $options = []) + public function count($condition = [], $options = [], $execOptions = []) { - return $this->database->createCommand()->count($this->name, $condition, $options); + return $this->database->createCommand()->count($this->name, $condition, $options, $execOptions); } /** @@ -351,9 +365,9 @@ public function count($condition = [], $options = []) * @return array|bool array of distinct values, or "false" on failure. * @throws Exception on failure. */ - public function distinct($column, $condition = [], $options = []) + public function distinct($column, $condition = [], $options = [], $execOptions = []) { - return $this->database->createCommand()->distinct($this->name, $column, $condition, $options); + return $this->database->createCommand()->distinct($this->name, $column, $condition, $options, $execOptions); } /** @@ -365,9 +379,9 @@ public function distinct($column, $condition = [], $options = []) * @return array|\MongoDB\Driver\Cursor the result of the aggregation. * @throws Exception on failure. */ - public function aggregate($pipelines, $options = []) + public function aggregate($pipelines, $options = [], $execOptions = []) { - return $this->database->createCommand()->aggregate($this->name, $pipelines, $options); + return $this->database->createCommand()->aggregate($this->name, $pipelines, $options, $execOptions); } /** @@ -385,9 +399,9 @@ public function aggregate($pipelines, $options = []) * @return array the result of the aggregation. * @throws Exception on failure. */ - public function group($keys, $initial, $reduce, $options = []) + public function group($keys, $initial, $reduce, $options = [], $execOptions = []) { - return $this->database->createCommand()->group($this->name, $keys, $initial, $reduce, $options); + return $this->database->createCommand()->group($this->name, $keys, $initial, $reduce, $options, $execOptions); } /** @@ -429,8 +443,8 @@ public function group($keys, $initial, $reduce, $options = []) * @return string|array the map reduce output collection name or output results. * @throws Exception on failure. */ - public function mapReduce($map, $reduce, $out, $condition = [], $options = []) + public function mapReduce($map, $reduce, $out, $condition = [], $options = [], $execOptions = []) { - return $this->database->createCommand()->mapReduce($this->name, $map, $reduce, $out, $condition, $options); + return $this->database->createCommand()->mapReduce($this->name, $map, $reduce, $out, $condition, $options, $execOptions); } } diff --git a/src/Command.php b/src/Command.php index e56b0b51c..85a629753 100644 --- a/src/Command.php +++ b/src/Command.php @@ -10,9 +10,6 @@ use MongoDB\BSON\ObjectID; use MongoDB\Driver\BulkWrite; use MongoDB\Driver\Exception\RuntimeException; -use MongoDB\Driver\ReadConcern; -use MongoDB\Driver\ReadPreference; -use MongoDB\Driver\WriteConcern; use MongoDB\Driver\WriteResult; use Yii; use yii\base\InvalidConfigException; @@ -73,107 +70,30 @@ class Command extends BaseObject */ public $document = []; - /** - * @var ReadPreference|int|string|null command read preference. - */ - private $_readPreference; - /** - * @var WriteConcern|int|string|null write concern to be used by this command. - */ - private $_writeConcern; - /** - * @var ReadConcern|string read concern to be used by this command - */ - private $_readConcern; - + public $globalExecOptions = []; /** - * Returns read preference for this command. - * @return ReadPreference read preference. - */ - public function getReadPreference() - { - if (!is_object($this->_readPreference)) { - if ($this->_readPreference === null) { - $this->_readPreference = $this->db->manager->getReadPreference(); - } elseif (is_scalar($this->_readPreference)) { - $this->_readPreference = new ReadPreference($this->_readPreference); - } - } - return $this->_readPreference; - } - - /** - * Sets read preference for this command. - * @param ReadPreference|int|string|null $readPreference read reference, it can be specified as - * instance of [[ReadPreference]] or scalar mode value, for example: `ReadPreference::RP_PRIMARY`. - * @return $this self reference. - */ - public function setReadPreference($readPreference) - { - $this->_readPreference = $readPreference; - return $this; - } - - /** - * Returns write concern for this command. - * @return WriteConcern|null write concern to be used in this command. - */ - public function getWriteConcern() - { - if ($this->_writeConcern !== null) { - if (is_scalar($this->_writeConcern)) { - $this->_writeConcern = new WriteConcern($this->_writeConcern); - } - } - return $this->_writeConcern; - } - - /** - * Sets write concern for this command. - * @param WriteConcern|int|string|null $writeConcern write concern, it can be an instance of [[WriteConcern]] - * or its scalar mode value, for example: `majority`. - * @return $this self reference - */ - public function setWriteConcern($writeConcern) - { - $this->_writeConcern = $writeConcern; - return $this; - } - - /** - * Retuns read concern for this command. - * @return ReadConcern|string read concern to be used in this command. - */ - public function getReadConcern() - { - if ($this->_readConcern !== null) { - if (is_scalar($this->_readConcern)) { - $this->_readConcern = new ReadConcern($this->_readConcern); - } - } - return $this->_readConcern; - } - - /** - * Sets read concern for this command. - * @param ReadConcern|string $readConcern read concern, it can be an instance of [[ReadConcern]] or - * scalar level value, for example: 'local'. - * @return $this self reference - */ - public function setReadConcern($readConcern) - { - $this->_readConcern = $readConcern; - return $this; + * prepare execOptions for some purpose + * @param array|object by reference see Connection::prepareExceOptions + */ + private function prepareExecOptions(&$execOptions){ + $execOptions = empty($execOptions) ? $this->globalExecOptions : $execOptions; + $this->db->prepareExecOptions($execOptions); } /** * Executes this command. + * @param array $execOptions options for executeCommand + * Note: "readConcern" and "writeConcern" options will not default to corresponding values from the MongoDB + * Connection URI nor will the MongoDB server version be taken into account + * @see https://www.php.net/manual/en/mongodb-driver-server.executebulkwrite.php#refsect1-mongodb-driver-server.executebulkwrite-parameters * @return \MongoDB\Driver\Cursor result cursor. * @throws Exception on failure. */ - public function execute() + public function execute($execOptions = []) { + $this->prepareExecOptions($execOptions); + $databaseName = $this->databaseName === null ? $this->db->defaultDatabaseName : $this->databaseName; $token = $this->log([$databaseName, 'command'], $this->document, __METHOD__); @@ -183,7 +103,7 @@ public function execute() $this->db->open(); $mongoCommand = new \MongoDB\Driver\Command($this->document); - $cursor = $this->db->manager->executeCommand($databaseName, $mongoCommand, $this->getReadPreference()); + $cursor = $this->db->manager->executeCommand($databaseName, $mongoCommand, $execOptions); $cursor->setTypeMap($this->db->typeMap); $this->endProfile($token, __METHOD__); @@ -204,11 +124,15 @@ public function execute() * - 'insertedIds' - contains inserted IDs. * - 'result' - [[\MongoDB\Driver\WriteResult]] instance. * + * @param array $execOptions options for executeBulkWrite + * @see https://www.php.net/manual/en/mongodb-driver-server.executebulkwrite.php#refsect1-mongodb-driver-server.executebulkwrite-parameters * @throws Exception on failure. * @throws InvalidConfigException on invalid [[document]] format. */ - public function executeBatch($collectionName, $options = []) + public function executeBatch($collectionName, $options = [], $execOptions = []) { + $this->prepareExecOptions($execOptions); + $databaseName = $this->databaseName === null ? $this->db->defaultDatabaseName : $this->databaseName; $token = $this->log([$databaseName, $collectionName, 'bulkWrite'], $this->document, __METHOD__); @@ -236,7 +160,7 @@ public function executeBatch($collectionName, $options = []) } $this->db->open(); - $writeResult = $this->db->manager->executeBulkWrite($databaseName . '.' . $collectionName, $batch, $this->getWriteConcern()); + $writeResult = $this->db->manager->executeBulkWrite($databaseName . '.' . $collectionName, $batch, $execOptions); $this->endProfile($token, __METHOD__); } catch (RuntimeException $e) { @@ -254,11 +178,15 @@ public function executeBatch($collectionName, $options = []) * Executes this command as a mongo query * @param string $collectionName collection name * @param array $options query options. + * @param array $execOptions options for executeQuery + * @see https://www.php.net/manual/en/mongodb-driver-server.executequery.php#refsect1-mongodb-driver-server.executequery-parameters * @return \MongoDB\Driver\Cursor result cursor. * @throws Exception on failure */ - public function query($collectionName, $options = []) + public function query($collectionName, $options = [], $execOptions = []) { + $this->prepareExecOptions($execOptions); + $databaseName = $this->databaseName === null ? $this->db->defaultDatabaseName : $this->databaseName; $token = $this->log( @@ -273,17 +201,12 @@ public function query($collectionName, $options = []) __METHOD__ ); - $readConcern = $this->getReadConcern(); - if ($readConcern !== null) { - $options['readConcern'] = $readConcern; - } - try { $this->beginProfile($token, __METHOD__); $query = new \MongoDB\Driver\Query($this->document, $options); $this->db->open(); - $cursor = $this->db->manager->executeQuery($databaseName . '.' . $collectionName, $query, $this->getReadPreference()); + $cursor = $this->db->manager->executeQuery($databaseName . '.' . $collectionName, $query, $execOptions); $cursor->setTypeMap($this->db->typeMap); $this->endProfile($token, __METHOD__); @@ -297,13 +220,14 @@ public function query($collectionName, $options = []) /** * Drops database associated with this command. + * @param array $execOptions -> goto Command::execute() * @return bool whether operation was successful. */ - public function dropDatabase() + public function dropDatabase($execOptions = []) { $this->document = $this->db->getQueryBuilder()->dropDatabase(); - $result = current($this->execute()->toArray()); + $result = current($this->execute($execOptions)->toArray()); return $result['ok'] > 0; } @@ -311,26 +235,28 @@ public function dropDatabase() * Creates new collection in database associated with this command.s * @param string $collectionName collection name * @param array $options collection options in format: "name" => "value" + * @param array $execOptions -> goto Command::execute() * @return bool whether operation was successful. */ - public function createCollection($collectionName, array $options = []) + public function createCollection($collectionName, array $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->createCollection($collectionName, $options); - $result = current($this->execute()->toArray()); + $result = current($this->execute($execOptions)->toArray()); return $result['ok'] > 0; } /** * Drops specified collection. * @param string $collectionName name of the collection to be dropped. + * @param array $execOptions -> goto Command::execute() * @return bool whether operation was successful. */ - public function dropCollection($collectionName) + public function dropCollection($collectionName, $execOptions = []) { $this->document = $this->db->getQueryBuilder()->dropCollection($collectionName); - $result = current($this->execute()->toArray()); + $result = current($this->execute($execOptions)->toArray()); return $result['ok'] > 0; } @@ -348,13 +274,14 @@ public function dropCollection($collectionName) * * See [[https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#options-for-all-index-types]] * for the full list of options. + * @param array $execOptions -> goto Command::execute() * @return bool whether operation was successful. */ - public function createIndexes($collectionName, $indexes) + public function createIndexes($collectionName, $indexes, $execOptions = []) { $this->document = $this->db->getQueryBuilder()->createIndexes($this->databaseName, $collectionName, $indexes); - $result = current($this->execute()->toArray()); + $result = current($this->execute($execOptions)->toArray()); return $result['ok'] > 0; } @@ -362,28 +289,30 @@ public function createIndexes($collectionName, $indexes) * Drops collection indexes by name. * @param string $collectionName collection name. * @param string $indexes wildcard for name of the indexes to be dropped. + * @param array $execOptions -> goto Command::execute() * @return array result data. */ - public function dropIndexes($collectionName, $indexes) + public function dropIndexes($collectionName, $indexes, $execOptions = []) { $this->document = $this->db->getQueryBuilder()->dropIndexes($collectionName, $indexes); - return current($this->execute()->toArray()); + return current($this->execute($execOptions)->toArray()); } /** * Returns information about current collection indexes. * @param string $collectionName collection name * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::execute() * @return array list of indexes info. * @throws Exception on failure. */ - public function listIndexes($collectionName, $options = []) + public function listIndexes($collectionName, $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->listIndexes($collectionName, $options); try { - $cursor = $this->execute(); + $cursor = $this->execute($execOptions); } catch (Exception $e) { // The server may return an error if the collection does not exist. $notFoundCodes = [ @@ -405,13 +334,14 @@ public function listIndexes($collectionName, $options = []) * @param string $collectionName collection name * @param array $condition filter condition * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::execute() * @return int records count */ - public function count($collectionName, $condition = [], $options = []) + public function count($collectionName, $condition = [], $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->count($collectionName, $condition, $options); - $result = current($this->execute()->toArray()); + $result = current($this->execute($execOptions)->toArray()); return $result['n']; } @@ -486,13 +416,14 @@ public function addDelete($condition, $options = []) * @param string $collectionName collection name * @param array $document document content * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::executeBatch() * @return ObjectID|bool inserted record ID, `false` - on failure. */ - public function insert($collectionName, $document, $options = []) + public function insert($collectionName, $document, $options = [], $execOptions = []) { $this->document = []; $this->addInsert($document); - $result = $this->executeBatch($collectionName, $options); + $result = $this->executeBatch($collectionName, $options, $execOptions); if ($result['result']->getInsertedCount() < 1) { return false; @@ -506,9 +437,10 @@ public function insert($collectionName, $document, $options = []) * @param string $collectionName collection name * @param array[] $documents documents list * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::executeBatch() * @return array|false list of inserted IDs, `false` on failure. */ - public function batchInsert($collectionName, $documents, $options = []) + public function batchInsert($collectionName, $documents, $options = [], $execOptions = []) { $this->document = []; foreach ($documents as $key => $document) { @@ -518,7 +450,7 @@ public function batchInsert($collectionName, $documents, $options = []) ]; } - $result = $this->executeBatch($collectionName, $options); + $result = $this->executeBatch($collectionName, $options, $execOptions); if ($result['result']->getInsertedCount() < 1) { return false; @@ -533,9 +465,10 @@ public function batchInsert($collectionName, $documents, $options = []) * @param array $condition filter condition * @param array $document data to be updated. * @param array $options update options. + * @param array $execOptions -> goto Command::executeBatch() * @return WriteResult write result. */ - public function update($collectionName, $condition, $document, $options = []) + public function update($collectionName, $condition, $document, $options = [], $execOptions = []) { $batchOptions = []; foreach (['bypassDocumentValidation'] as $name) { @@ -547,7 +480,7 @@ public function update($collectionName, $condition, $document, $options = []) $this->document = []; $this->addUpdate($condition, $document, $options); - $result = $this->executeBatch($collectionName, $batchOptions); + $result = $this->executeBatch($collectionName, $batchOptions, $execOptions); return $result['result']; } @@ -557,9 +490,10 @@ public function update($collectionName, $condition, $document, $options = []) * @param string $collectionName collection name. * @param array $condition filter condition. * @param array $options delete options. + * @param array $execOptions -> goto Command::executeBatch() * @return WriteResult write result. */ - public function delete($collectionName, $condition, $options = []) + public function delete($collectionName, $condition, $options = [], $execOptions = []) { $batchOptions = []; foreach (['bypassDocumentValidation'] as $name) { @@ -571,7 +505,7 @@ public function delete($collectionName, $condition, $options = []) $this->document = []; $this->addDelete($condition, $options); - $result = $this->executeBatch($collectionName, $batchOptions); + $result = $this->executeBatch($collectionName, $batchOptions, $execOptions); return $result['result']; } @@ -581,9 +515,10 @@ public function delete($collectionName, $condition, $options = []) * @param string $collectionName collection name * @param array $condition filter condition * @param array $options query options. + * @param array $execOptions -> goto Command::executeQuery() * @return \MongoDB\Driver\Cursor result cursor. */ - public function find($collectionName, $condition, $options = []) + public function find($collectionName, $condition, $options = [], $execOptions = []) { $queryBuilder = $this->db->getQueryBuilder(); @@ -612,7 +547,7 @@ public function find($collectionName, $condition, $options = []) } } - return $this->query($collectionName, $options); + return $this->query($collectionName, $options, $execOptions); } /** @@ -621,12 +556,13 @@ public function find($collectionName, $condition, $options = []) * @param array $condition query condition * @param array $update update criteria * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::execute() * @return array|null the original document, or the modified document when $options['new'] is set. */ - public function findAndModify($collectionName, $condition = [], $update = [], $options = []) + public function findAndModify($collectionName, $condition = [], $update = [], $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->findAndModify($collectionName, $condition, $update, $options); - $cursor = $this->execute(); + $cursor = $this->execute($execOptions); $result = current($cursor->toArray()); @@ -643,12 +579,13 @@ public function findAndModify($collectionName, $condition = [], $update = [], $o * @param string $fieldName field name to use. * @param array $condition query parameters. * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions -> goto Command::execute() * @return array array of distinct values, or "false" on failure. */ - public function distinct($collectionName, $fieldName, $condition = [], $options = []) + public function distinct($collectionName, $fieldName, $condition = [], $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->distinct($collectionName, $fieldName, $condition, $options); - $cursor = $this->execute(); + $cursor = $this->execute($execOptions); $result = current($cursor->toArray()); @@ -672,12 +609,13 @@ public function distinct($collectionName, $fieldName, $condition = [], $options * @param array $options optional parameters to the group command. Valid options include: * - condition - criteria for including a document in the aggregation. * - finalize - function called once per unique key that takes the final output of the reduce function. + * @param array $execOptions -> goto Command::execute() * @return array the result of the aggregation. */ - public function group($collectionName, $keys, $initial, $reduce, $options = []) + public function group($collectionName, $keys, $initial, $reduce, $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->group($collectionName, $keys, $initial, $reduce, $options); - $cursor = $this->execute(); + $cursor = $this->execute($execOptions); $result = current($cursor->toArray()); @@ -705,12 +643,13 @@ public function group($collectionName, $keys, $initial, $reduce, $options = []) * - jsMode: bool, specifies whether to convert intermediate data into BSON format between the execution of the map and reduce functions. * - verbose: bool, specifies whether to include the timing information in the result information. * + * @param array $execOptions -> goto Command::execute() * @return string|array the map reduce output collection name or output results. */ - public function mapReduce($collectionName, $map, $reduce, $out, $condition = [], $options = []) + public function mapReduce($collectionName, $map, $reduce, $out, $condition = [], $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->mapReduce($collectionName, $map, $reduce, $out, $condition, $options); - $cursor = $this->execute(); + $cursor = $this->execute($execOptions); $result = current($cursor->toArray()); @@ -724,9 +663,10 @@ public function mapReduce($collectionName, $map, $reduce, $out, $condition = [], * @param string $collectionName collection name * @param array $pipelines list of pipeline operators. * @param array $options optional parameters. + * @param array $execOptions -> goto Command::execute() * @return array|\MongoDB\Driver\Cursor aggregation result. */ - public function aggregate($collectionName, $pipelines, $options = []) + public function aggregate($collectionName, $pipelines, $options = [], $execOptions = []) { if (empty($options['cursor'])) { $returnCursor = false; @@ -736,7 +676,7 @@ public function aggregate($collectionName, $pipelines, $options = []) } $this->document = $this->db->getQueryBuilder()->aggregate($collectionName, $pipelines, $options); - $cursor = $this->execute(); + $cursor = $this->execute($execOptions); if ($returnCursor) { return $cursor; @@ -749,12 +689,13 @@ public function aggregate($collectionName, $pipelines, $options = []) * Return an explanation of the query, often useful for optimization and debugging. * @param string $collectionName collection name * @param array $query query document. + * @param array $execOptions -> goto Command::execute() * @return array explanation of the query. */ - public function explain($collectionName, $query) + public function explain($collectionName, $query, $execOptions = []) { $this->document = $this->db->getQueryBuilder()->explain($collectionName, $query); - $cursor = $this->execute(); + $cursor = $this->execute($execOptions); return current($cursor->toArray()); } @@ -763,16 +704,17 @@ public function explain($collectionName, $query) * Returns the list of available databases. * @param array $condition filter condition. * @param array $options options list. + * @param array $execOptions -> goto Command::execute() * @return array database information */ - public function listDatabases($condition = [], $options = []) + public function listDatabases($condition = [], $options = [], $execOptions = []) { if ($this->databaseName === null) { $this->databaseName = 'admin'; } $this->document = $this->db->getQueryBuilder()->listDatabases($condition, $options); - $cursor = $this->execute(); + $cursor = $this->execute($execOptions); $result = current($cursor->toArray()); if (empty($result['databases'])) { @@ -785,12 +727,13 @@ public function listDatabases($condition = [], $options = []) * Returns the list of available collections. * @param array $condition filter condition. * @param array $options options list. + * @param array $execOptions -> goto Command::execute() * @return array collections information. */ - public function listCollections($condition = [], $options = []) + public function listCollections($condition = [], $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->listCollections($condition, $options); - $cursor = $this->execute(); + $cursor = $this->execute($execOptions); return $cursor->toArray(); } @@ -839,4 +782,4 @@ protected function endProfile($token, $category) Yii::endProfile($token, $category); } } -} +} \ No newline at end of file diff --git a/src/Connection.php b/src/Connection.php index 590f29d82..294635f90 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -10,6 +10,9 @@ use MongoDB\Driver\Manager; use yii\base\Component; use yii\base\InvalidConfigException; +use \MongoDB\Driver\ReadConcern; +use \MongoDB\Driver\WriteConcern; +use \MongoDB\Driver\ReadPreference; use Yii; /** @@ -83,6 +86,26 @@ class Connection extends Component * @event Event an event that is triggered after a DB connection is established */ const EVENT_AFTER_OPEN = 'afterOpen'; + /** + * @event yii\base\Event an event that is triggered right before a mongo client session is started + */ + const EVENT_START_SESSION = 'startSession'; + /** + * @event yii\base\Event an event that is triggered right after a mongo client session is ended + */ + const EVENT_END_SESSION = 'endSession'; + /** + * @event yii\base\Event an event that is triggered right before a transaction is started + */ + const EVENT_START_TRANSACTION = 'startTransaction'; + /** + * @event yii\base\Event an event that is triggered right after a transaction is committed + */ + const EVENT_COMMIT_TRANSACTION = 'commitTransaction'; + /** + * @event yii\base\Event an event that is triggered right after a transaction is rolled back + */ + const EVENT_ROLLBACK_TRANSACTION = 'rollbackTransaction'; /** * @var string host:port @@ -155,6 +178,8 @@ class Connection extends Component */ public $fileStreamWrapperClass = 'yii\mongodb\file\StreamWrapper'; + public $globalExecOptions = []; + /** * @var string name of the MongoDB database to use by default. * If this field left blank, connection instance will attempt to determine it from @@ -412,6 +437,7 @@ public function createCommand($document = [], $databaseName = null) 'db' => $this, 'databaseName' => $databaseName, 'document' => $document, + 'globalExecOptions' => $this->globalExecOptions ]); } @@ -432,4 +458,176 @@ public function registerFileStreamWrapper($force = false) return $this->fileStreamProtocol; } + + /** + * set global execOptions for Command::execute() and Command::executeBatch() and Command::query() + * this options when set if internal $execOptions is not set. + * @param array $execOptions see docs of Command::execute() and Command::executeBatch() and Command::query() + * @return $this + */ + public function execOptions($execOptions){ + if(empty($execOptions)) + $this->globalExecOptions = []; + else + $this->globalExecOptions = array_merge_recursive($this->globalExecOptions, $execOptions); + return $this; + } + + /** + * preapare execOptions for some purpose + * @param array|object by reference + * convert string option to object + * ['readConcern' => 'snapshot'] > ['readConcern' => new \MongoDB\Driver\ReadConcern('snapshot')] + * ['writeConcern' => 'majority'] > ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority')] + * ['writeConcern' => ['majority',true]] > ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority',true)] + */ + public static function prepareExecOptions(&$execOptions){ + + #convert readConcern option + if(array_key_exists('readConcern', $execOptions) && is_string($execOptions['readConcern'])) + $execOptions['readConcern'] = new ReadConcern($execOptions['readConcern']); + + #convert writeConcern option + if(array_key_exists('writeConcern', $execOptions)){ + if(is_string($execOptions['writeConcern'])) + $execOptions['writeConcern'] = new WriteConcern($execOptions['writeConcern']); + elseif(is_array($execOptions['writeConcern'])) + $execOptions['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($execOptions['writeConcern']); + } + + #conver readPreference option + if(array_key_exists('readPreference', $execOptions)){ + if(is_string($execOptions['readPreference'])) + $execOptions['readPreference'] = new ReadPreference($execOptions['readPreference']); + elseif(is_array($execOptions['readPreference'])) + $execOptions['readPreference'] = (new \ReflectionClass('\MongoDB\Driver\ReadPreference'))->newInstanceArgs($execOptions['readPreference']); + } + + #convert session option + if(array_key_exists('session',$execOptions)) + $execOptions['session'] = $execOptions['session']->mongoSession; + + #convert defaultTransactionOptions for MongoDB\Driver\Manager::startSession + if( + array_key_exists('defaultTransactionOptions',$execOptions) && + array_key_exists('readConcern',$execOptions['defaultTransactionOptions']) && + is_string($execOptions['defaultTransactionOptions']['readConcern']) + ) + $execOptions['defaultTransactionOptions']['readConcern'] = new ReadConcern($execOptions['defaultTransactionOptions']['readConcern']); + + if(array_key_exists('defaultTransactionOptions',$execOptions) && array_key_exists('writeConcern',$execOptions['defaultTransactionOptions'])){ + if(is_string($execOptions['defaultTransactionOptions']['writeConcern'])) + $execOptions['defaultTransactionOptions']['writeConcern'] = new WriteConcern($execOptions['defaultTransactionOptions']['writeConcern']); + else if(is_array($execOptions['defaultTransactionOptions']['writeConcern'])) + $execOptions['defaultTransactionOptions']['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($execOptions['defaultTransactionOptions']['writeConcern']); + } + + if(array_key_exists('defaultTransactionOptions',$execOptions) && array_key_exists('readPreference',$execOptions['defaultTransactionOptions'])){ + if(is_string($execOptions['defaultTransactionOptions']['readPreference'])) + $execOptions['defaultTransactionOptions']['readPreference'] = new ReadPreference($execOptions['defaultTransactionOptions']['readPreference']); + else if(is_array($execOptions['defaultTransactionOptions']['readPreference'])) + $execOptions['defaultTransactionOptions']['readPreference'] = (new \ReflectionClass('\MongoDB\Driver\ReadPreference'))->newInstanceArgs($execOptions['defaultTransactionOptions']['readPreference']); + } + } + + /** + * start new session for current connection + * @param array $sessionOptions see doc of ClientSession::start() + * return ClientSession + */ + public function startSession($sessionOptions = []){ + return ClientSession::start($this, $sessionOptions); + } + + /** + * check if current connection is in session + * return bool + */ + public function getInSession(){ + return array_key_exists('session',$this->globalExecOptions); + } + + /** + * return current session + * return ClientSession|null + */ + public function getSession(){ + return $this->getInSession() ? $this->globalExecOptions['session'] : null; + } + + /** + * start transaction with three step : + * - start new session + * - start transaction of new session + * - set new session to current command + * @param array $transactionOptions see doc of Transaction::start() + * @param array $sessionOptions see doc of ClientSession::start() + * return ClientSession + */ + public function startTransaction($transactionOptions = [], $sessionOptions = []){ + $newClientSession = $this->startSession($sessionOptions); + $newClientSession->getTransaction()->start($transactionOptions); + $this->withSession($newClientSession); + return $newClientSession; + } + + /** + * commit transaction in current session + */ + public function commitTransaction(){ + if(!$this->getInSession()) + throw new Exception('You can\'t commit transaction because current connection is\'t in a session.'); + if(!$this->getSession()->getHasTransaction()) + throw new Exception('You can\'t commit transaction because transaction not started in current session.'); + $this->getSession()->transaction->commit(); + } + + /** + * commit transaction in current session + */ + public function rollBackTransaction(){ + if(!$this->getInSession()) + throw new Exception('You can\'t roll back transaction because current connection is\'t in a session.'); + if(!$this->getSession()->getHasTransaction()) + throw new Exception('You can\'t roll back transaction because transaction not started in current session.'); + $this->getSession()->transaction->rollBack(); + } + + /** + * change current session of command (or drop session) + * @param ClientSession|null $clientSession new instance of ClientSession for replace + * return $this + */ + public function withSession($clientSession){ + #drop session + if(empty($clientSession)) + unset($this->globalExecOptions['session']); + else + $this->globalExecOptions['session'] = $clientSession; + return $this; + } + + /** + * easy start and commit transaction + * @param callable $actions your block of code must be run after transaction started and before commit + * if $actions return false then transaction rolled back. + * @param array $transactionOptions see doc of Transaction::start() + * @param array $sessionOptions see doc of ClientSession::start() + * return $this + */ + public function transaction(callable $actions, $transactionOptions = [], $sessionOptions = []){ + $newClientSession = $this->startTransaction($transactionOptions, $sessionOptions); + try { + $result = call_user_func($actions, $newClientSession); + if($newClientSession->getTransaction()->getIsActive()) + if($result === false) + $newClientSession->getTransaction()->rollBack(); + else + $newClientSession->getTransaction()->commit(); + } catch (\Exception $e){ + if($newClientSession->getTransaction()->getIsActive()) + $newClientSession->getTransaction()->rollBack(); + throw $e; + } + } } diff --git a/src/Database.php b/src/Database.php index 395aac79e..0eba51b5a 100644 --- a/src/Database.php +++ b/src/Database.php @@ -115,35 +115,38 @@ public function createCommand($document = []) * you need to create collection with the specific options. * @param string $name name of the collection * @param array $options collection options in format: "name" => "value" + * @param array $execOptions -> goto Command::execute() * @return bool whether operation was successful. * @throws Exception on failure. */ - public function createCollection($name, $options = []) + public function createCollection($name, $options = [], $execOptions = []) { - return $this->createCommand()->createCollection($name, $options); + return $this->createCommand()->createCollection($name, $options, $execOptions); } /** * Drops specified collection. * @param string $name name of the collection + * @param array $execOptions -> goto Command::execute() * @return bool whether operation was successful. * @since 2.1 */ - public function dropCollection($name) + public function dropCollection($name, $execOptions = []) { - return $this->createCommand()->dropCollection($name); + return $this->createCommand()->dropCollection($name, $execOptions); } /** * Returns the list of available collections in this database. * @param array $condition filter condition. * @param array $options options list. + * @param array $execOptions -> goto Command::execute() * @return array collections information. * @since 2.1.1 */ - public function listCollections($condition = [], $options = []) + public function listCollections($condition = [], $options = [], $execOptions = []) { - return $this->createCommand()->listCollections($condition, $options); + return $this->createCommand()->listCollections($condition, $options, $execOptions); } /** diff --git a/src/Transaction.php b/src/Transaction.php new file mode 100644 index 000000000..9e46e8e7e --- /dev/null +++ b/src/Transaction.php @@ -0,0 +1,85 @@ + + */ +class Transaction extends \yii\base\BaseObject +{ + + /** + * @var MongoDB\Driver\Session class represents a client session and Commands, queries, and write operations may then be associated the session. + * @see https://www.php.net/manual/en/class.mongodb-driver-session.php + */ + public $clientSession; + + /** + * Returns a value indicating whether this transaction is active. + * @return bool whether this transaction is active. Only an active transaction + * can [[commit()]] or [[rollBack()]]. + */ + public function getIsActive(){ + return $this->clientSession->db->getIsActive() && $this->clientSession->mongoSession->isInTransaction(); + } + + /** + * Start a transaction if session is not in transaction process. + * @see https://www.php.net/manual/en/mongodb-driver-session.starttransaction.php + * @param array $transactionOptions Options can be passed as argument to this method. + * Each element in this options array overrides the corresponding option from the "sessionOptions" option, + * if set when starting the session with ClientSession::start(). + * @see https://www.php.net/manual/en/mongodb-driver-session.starttransaction.php#refsect1-mongodb-driver-session.starttransaction-parameters + */ + public function start($transactionOptions = []){ + Connection::prepareExecOptions($transactionOptions); + yii::trace('Starting mongodb transaction ...', __METHOD__); + if($this->clientSession->mongoSession->isInTransaction()) + throw new Exception('Nested transaction not supported'); + $this->clientSession->db->trigger(Connection::EVENT_START_TRANSACTION); + $this->clientSession->mongoSession->startTransaction($transactionOptions); + yii::trace('MongoDB transaction started.', __METHOD__); + } + + /** + * Commit a transaction. + * @see https://www.php.net/manual/en/mongodb-driver-session.committransaction.php + */ + public function commit(){ + yii::trace('Committing mongodb transaction ...', __METHOD__); + $this->clientSession->mongoSession->commitTransaction(); + yii::trace('Commit mongodb transaction.', __METHOD__); + $this->clientSession->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); + } + + /** + * Rolls back a transaction. + * @see https://www.php.net/manual/en/mongodb-driver-session.aborttransaction.php + */ + public function rollBack(){ + yii::trace('Rolling back mongodb transaction ...', __METHOD__); + $this->clientSession->mongoSession->abortTransaction(); + yii::trace('Roll back mongodb transaction.', __METHOD__); + $this->clientSession->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); + } +} \ No newline at end of file From 9da28a245aa9fb52fe7ee4f31a2a917dc1733f3c Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Fri, 24 Jan 2020 07:51:44 +0330 Subject: [PATCH 02/82] better structure for options --- src/ClientSession.php | 57 ++++++++++++++++++++++++++++++++++++-- src/Command.php | 42 +++++++++++++++++++++++++++- src/Connection.php | 64 ++----------------------------------------- src/Transaction.php | 2 +- 4 files changed, 99 insertions(+), 66 deletions(-) diff --git a/src/ClientSession.php b/src/ClientSession.php index 347de8e17..0a2aebb31 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -8,7 +8,9 @@ namespace yii\mongodb; use Yii; - +use MongoDB\Driver\ReadConcern; +use MongoDB\Driver\WriteConcern; +use MongoDB\Driver\ReadPreference; /** * ClientSession represents a client session and Commands, queries, and write operations may then be associated the session. @@ -39,6 +41,57 @@ class ClientSession extends \yii\base\BaseObject */ private $_transaction = null; + /** + * preapare options for some purpose + * @param array by reference + * convert string option to object + * [ + * 'defaultTransactionOptions' => [ + * 'readConcern' => 'snapshot', + * 'writeConcern' => 'majority', + * 'writeConcern' => ['majority',true], + * 'readPreference' => 'primary', + * ], + * ] + * convert to : + * [ + * 'defaultTransactionOptions' => [ + * 'readConcern' => new \MongoDB\Driver\ReadConcern('snapshot'), + * 'writeConcern' => new \MongoDB\Driver\WriteConcern('majority'), + * 'writeConcern' => new \MongoDB\Driver\WriteConcern('majority',true), + * 'readPreference' => new \MongoDB\Driver\ReadPreference('primary'), + * ], + * ] + */ + public static function prepareOptions(&$options){ + + if(array_key_exists('defaultTransactionOptions',$options)){ + + #convert readConcern + if( + array_key_exists('readConcern',$options['defaultTransactionOptions']) && + is_string($options['defaultTransactionOptions']['readConcern']) + ) + $options['defaultTransactionOptions']['readConcern'] = new ReadConcern($options['defaultTransactionOptions']['readConcern']); + + #convert writeConcern + if(array_key_exists('writeConcern',$options['defaultTransactionOptions'])){ + if(is_string($options['defaultTransactionOptions']['writeConcern'])) + $options['defaultTransactionOptions']['writeConcern'] = new WriteConcern($options['defaultTransactionOptions']['writeConcern']); + else if(is_array($options['defaultTransactionOptions']['writeConcern'])) + $options['defaultTransactionOptions']['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($options['defaultTransactionOptions']['writeConcern']); + } + + #convert readPreference + if(array_key_exists('readPreference',$options['defaultTransactionOptions'])){ + if(is_string($options['defaultTransactionOptions']['readPreference'])) + $options['defaultTransactionOptions']['readPreference'] = new ReadPreference($options['defaultTransactionOptions']['readPreference']); + else if(is_array($options['defaultTransactionOptions']['readPreference'])) + $options['defaultTransactionOptions']['readPreference'] = (new \ReflectionClass('\MongoDB\Driver\ReadPreference'))->newInstanceArgs($options['defaultTransactionOptions']['readPreference']); + } + } + } + /** * Start a new session in a connection. * @param Connection $db @@ -47,7 +100,7 @@ class ClientSession extends \yii\base\BaseObject * @return ClientSession return new session base on a session options for the given connection */ public static function start($db, $sessionOptions = []){ - Connection::prepareExecOptions($sessionOptions); + self::prepareOptions($sessionOptions); Yii::trace('Starting mongodb session ...', __METHOD__); $db->trigger(Connection::EVENT_START_SESSION); $newSession = new self([ diff --git a/src/Command.php b/src/Command.php index 85a629753..9b27c9489 100644 --- a/src/Command.php +++ b/src/Command.php @@ -11,6 +11,9 @@ use MongoDB\Driver\BulkWrite; use MongoDB\Driver\Exception\RuntimeException; use MongoDB\Driver\WriteResult; +use MongoDB\Driver\ReadConcern; +use MongoDB\Driver\WriteConcern; +use MongoDB\Driver\ReadPreference; use Yii; use yii\base\InvalidConfigException; use yii\base\BaseObject; @@ -78,7 +81,44 @@ class Command extends BaseObject */ private function prepareExecOptions(&$execOptions){ $execOptions = empty($execOptions) ? $this->globalExecOptions : $execOptions; - $this->db->prepareExecOptions($execOptions); + + self::prepareCPOptions($execOptions); + + #convert session option + if(array_key_exists('session',$execOptions) && $execOptions['session'] instanceof ClientSession) + $execOptions['session'] = $execOptions['session']->mongoSession; + } + + /** + * preapare Concern and Preference options for easy use + * @param array|object by reference + * convert string option to object + * ['readConcern' => 'snapshot'] > ['readConcern' => new \MongoDB\Driver\ReadConcern('snapshot')] + * ['writeConcern' => 'majority'] > ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority')] + * ['writeConcern' => ['majority',true]] > ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority',true)] + * ['readPreference' => 'snapshot'] > ['readPreference' => new \MongoDB\Driver\ReadPreference('primary')] + */ + public static function prepareCPOptions(&$options){ + + #convert readConcern option + if(array_key_exists('readConcern', $options) && is_string($options['readConcern'])) + $options['readConcern'] = new ReadConcern($options['readConcern']); + + #convert writeConcern option + if(array_key_exists('writeConcern', $options)){ + if(is_string($options['writeConcern'])) + $options['writeConcern'] = new WriteConcern($options['writeConcern']); + elseif(is_array($options['writeConcern'])) + $options['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($options['writeConcern']); + } + + #conver readPreference option + if(array_key_exists('readPreference', $options)){ + if(is_string($options['readPreference'])) + $options['readPreference'] = new ReadPreference($options['readPreference']); + elseif(is_array($options['readPreference'])) + $options['readPreference'] = (new \ReflectionClass('\MongoDB\Driver\ReadPreference'))->newInstanceArgs($options['readPreference']); + } } /** diff --git a/src/Connection.php b/src/Connection.php index 294635f90..6e16eee52 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -10,9 +10,6 @@ use MongoDB\Driver\Manager; use yii\base\Component; use yii\base\InvalidConfigException; -use \MongoDB\Driver\ReadConcern; -use \MongoDB\Driver\WriteConcern; -use \MongoDB\Driver\ReadPreference; use Yii; /** @@ -473,63 +470,6 @@ public function execOptions($execOptions){ return $this; } - /** - * preapare execOptions for some purpose - * @param array|object by reference - * convert string option to object - * ['readConcern' => 'snapshot'] > ['readConcern' => new \MongoDB\Driver\ReadConcern('snapshot')] - * ['writeConcern' => 'majority'] > ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority')] - * ['writeConcern' => ['majority',true]] > ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority',true)] - */ - public static function prepareExecOptions(&$execOptions){ - - #convert readConcern option - if(array_key_exists('readConcern', $execOptions) && is_string($execOptions['readConcern'])) - $execOptions['readConcern'] = new ReadConcern($execOptions['readConcern']); - - #convert writeConcern option - if(array_key_exists('writeConcern', $execOptions)){ - if(is_string($execOptions['writeConcern'])) - $execOptions['writeConcern'] = new WriteConcern($execOptions['writeConcern']); - elseif(is_array($execOptions['writeConcern'])) - $execOptions['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($execOptions['writeConcern']); - } - - #conver readPreference option - if(array_key_exists('readPreference', $execOptions)){ - if(is_string($execOptions['readPreference'])) - $execOptions['readPreference'] = new ReadPreference($execOptions['readPreference']); - elseif(is_array($execOptions['readPreference'])) - $execOptions['readPreference'] = (new \ReflectionClass('\MongoDB\Driver\ReadPreference'))->newInstanceArgs($execOptions['readPreference']); - } - - #convert session option - if(array_key_exists('session',$execOptions)) - $execOptions['session'] = $execOptions['session']->mongoSession; - - #convert defaultTransactionOptions for MongoDB\Driver\Manager::startSession - if( - array_key_exists('defaultTransactionOptions',$execOptions) && - array_key_exists('readConcern',$execOptions['defaultTransactionOptions']) && - is_string($execOptions['defaultTransactionOptions']['readConcern']) - ) - $execOptions['defaultTransactionOptions']['readConcern'] = new ReadConcern($execOptions['defaultTransactionOptions']['readConcern']); - - if(array_key_exists('defaultTransactionOptions',$execOptions) && array_key_exists('writeConcern',$execOptions['defaultTransactionOptions'])){ - if(is_string($execOptions['defaultTransactionOptions']['writeConcern'])) - $execOptions['defaultTransactionOptions']['writeConcern'] = new WriteConcern($execOptions['defaultTransactionOptions']['writeConcern']); - else if(is_array($execOptions['defaultTransactionOptions']['writeConcern'])) - $execOptions['defaultTransactionOptions']['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($execOptions['defaultTransactionOptions']['writeConcern']); - } - - if(array_key_exists('defaultTransactionOptions',$execOptions) && array_key_exists('readPreference',$execOptions['defaultTransactionOptions'])){ - if(is_string($execOptions['defaultTransactionOptions']['readPreference'])) - $execOptions['defaultTransactionOptions']['readPreference'] = new ReadPreference($execOptions['defaultTransactionOptions']['readPreference']); - else if(is_array($execOptions['defaultTransactionOptions']['readPreference'])) - $execOptions['defaultTransactionOptions']['readPreference'] = (new \ReflectionClass('\MongoDB\Driver\ReadPreference'))->newInstanceArgs($execOptions['defaultTransactionOptions']['readPreference']); - } - } - /** * start new session for current connection * @param array $sessionOptions see doc of ClientSession::start() @@ -559,7 +499,7 @@ public function getSession(){ * start transaction with three step : * - start new session * - start transaction of new session - * - set new session to current command + * - set new session to current connection * @param array $transactionOptions see doc of Transaction::start() * @param array $sessionOptions see doc of ClientSession::start() * return ClientSession @@ -583,7 +523,7 @@ public function commitTransaction(){ } /** - * commit transaction in current session + * rollback transaction in current session */ public function rollBackTransaction(){ if(!$this->getInSession()) diff --git a/src/Transaction.php b/src/Transaction.php index 9e46e8e7e..6b311336c 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -52,7 +52,7 @@ public function getIsActive(){ * @see https://www.php.net/manual/en/mongodb-driver-session.starttransaction.php#refsect1-mongodb-driver-session.starttransaction-parameters */ public function start($transactionOptions = []){ - Connection::prepareExecOptions($transactionOptions); + Command::prepareCPOptions($transactionOptions); yii::trace('Starting mongodb transaction ...', __METHOD__); if($this->clientSession->mongoSession->isInTransaction()) throw new Exception('Nested transaction not supported'); From b2a97e167e3a1f81494efae50a97ee10579f5ef8 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Fri, 24 Jan 2020 08:01:49 +0330 Subject: [PATCH 03/82] use yii::debug instead of yii::trace `trace()` deprecated since 2.0.14 --- src/ClientSession.php | 4 ++-- src/Connection.php | 4 ++-- src/Transaction.php | 12 ++++++------ src/rbac/MongoDbManager.php | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ClientSession.php b/src/ClientSession.php index 0a2aebb31..ceda9f421 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -101,13 +101,13 @@ public static function prepareOptions(&$options){ */ public static function start($db, $sessionOptions = []){ self::prepareOptions($sessionOptions); - Yii::trace('Starting mongodb session ...', __METHOD__); + Yii::debug('Starting mongodb session ...', __METHOD__); $db->trigger(Connection::EVENT_START_SESSION); $newSession = new self([ 'db' => $db, 'mongoSession' => $db->manager->startSession($sessionOptions), ]); - Yii::trace('MongoDB session started.', __METHOD__); + Yii::debug('MongoDB session started.', __METHOD__); return $newSession; } diff --git a/src/Connection.php b/src/Connection.php index 6e16eee52..324f85719 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -371,7 +371,7 @@ public function open() } $token = 'Opening MongoDB connection: ' . $this->dsn; try { - Yii::trace($token, __METHOD__); + Yii::debug($token, __METHOD__); Yii::beginProfile($token, __METHOD__); $options = $this->options; @@ -402,7 +402,7 @@ public function open() public function close() { if ($this->manager !== null) { - Yii::trace('Closing MongoDB connection: ' . $this->dsn, __METHOD__); + Yii::debug('Closing MongoDB connection: ' . $this->dsn, __METHOD__); $this->manager = null; foreach ($this->_databases as $database) { $database->clearCollections(); diff --git a/src/Transaction.php b/src/Transaction.php index 6b311336c..9d080e179 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -53,12 +53,12 @@ public function getIsActive(){ */ public function start($transactionOptions = []){ Command::prepareCPOptions($transactionOptions); - yii::trace('Starting mongodb transaction ...', __METHOD__); + Yii::debug('Starting mongodb transaction ...', __METHOD__); if($this->clientSession->mongoSession->isInTransaction()) throw new Exception('Nested transaction not supported'); $this->clientSession->db->trigger(Connection::EVENT_START_TRANSACTION); $this->clientSession->mongoSession->startTransaction($transactionOptions); - yii::trace('MongoDB transaction started.', __METHOD__); + Yii::debug('MongoDB transaction started.', __METHOD__); } /** @@ -66,9 +66,9 @@ public function start($transactionOptions = []){ * @see https://www.php.net/manual/en/mongodb-driver-session.committransaction.php */ public function commit(){ - yii::trace('Committing mongodb transaction ...', __METHOD__); + Yii::debug('Committing mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->commitTransaction(); - yii::trace('Commit mongodb transaction.', __METHOD__); + Yii::debug('Commit mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); } @@ -77,9 +77,9 @@ public function commit(){ * @see https://www.php.net/manual/en/mongodb-driver-session.aborttransaction.php */ public function rollBack(){ - yii::trace('Rolling back mongodb transaction ...', __METHOD__); + Yii::debug('Rolling back mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->abortTransaction(); - yii::trace('Roll back mongodb transaction.', __METHOD__); + Yii::debug('Roll back mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); } } \ No newline at end of file diff --git a/src/rbac/MongoDbManager.php b/src/rbac/MongoDbManager.php index 78f73113d..97b96f9ac 100644 --- a/src/rbac/MongoDbManager.php +++ b/src/rbac/MongoDbManager.php @@ -133,7 +133,7 @@ protected function checkAccessFromCache($user, $itemName, $params, $assignments) $item = $this->items[$itemName]; - Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__); + Yii::debug($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__); if (!$this->executeRule($user, $item, $params)) { return false; @@ -172,7 +172,7 @@ protected function checkAccessRecursive($user, $itemName, $params, $assignments) return false; } - Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__); + Yii::debug($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__); if (!$this->executeRule($user, $item, $params)) { return false; From 9c3948196d3683b57f5702cd4b37503a45b17fae Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Mon, 27 Jan 2020 23:32:28 +0330 Subject: [PATCH 04/82] add profile to transaction add ClientSession::getId() --- src/ClientSession.php | 9 +++++++++ src/Transaction.php | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/ClientSession.php b/src/ClientSession.php index ceda9f421..bbd703dc1 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -92,6 +92,15 @@ public static function prepareOptions(&$options){ } } + /** + * Returns the logical session ID as string for this session, which may be used to identify this session's operations on the server. + * @see https://www.php.net/manual/en/mongodb-driver-session.getlogicalsessionid.php + * @return string + */ + public function getId(){ + return $this->mongoSession->getLogicalSessionId()->id->jsonSerialize()['$binary']; + } + /** * Start a new session in a connection. * @param Connection $db diff --git a/src/Transaction.php b/src/Transaction.php index 9d080e179..86fa96e4b 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -57,6 +57,8 @@ public function start($transactionOptions = []){ if($this->clientSession->mongoSession->isInTransaction()) throw new Exception('Nested transaction not supported'); $this->clientSession->db->trigger(Connection::EVENT_START_TRANSACTION); + if($this->mongoSession->db->enableLogging) + Yii::beginProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); $this->clientSession->mongoSession->startTransaction($transactionOptions); Yii::debug('MongoDB transaction started.', __METHOD__); } @@ -68,6 +70,8 @@ public function start($transactionOptions = []){ public function commit(){ Yii::debug('Committing mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->commitTransaction(); + if($this->mongoSession->db->enableLogging) + Yii::endProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); Yii::debug('Commit mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); } @@ -79,6 +83,8 @@ public function commit(){ public function rollBack(){ Yii::debug('Rolling back mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->abortTransaction(); + if($this->mongoSession->db->enableLogging) + Yii::endProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); Yii::debug('Roll back mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); } From 2c6188686760b79e446386a7a982cce5c8526f7d Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sat, 1 Feb 2020 13:28:43 +0330 Subject: [PATCH 05/82] fix bug --- src/Transaction.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Transaction.php b/src/Transaction.php index 86fa96e4b..3c9699f73 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -40,7 +40,7 @@ class Transaction extends \yii\base\BaseObject * can [[commit()]] or [[rollBack()]]. */ public function getIsActive(){ - return $this->clientSession->db->getIsActive() && $this->clientSession->mongoSession->isInTransaction(); + return $this->clientSession->db->getIsActive() && $this->clientSession->GetHasTransaction(); } /** @@ -54,10 +54,10 @@ public function getIsActive(){ public function start($transactionOptions = []){ Command::prepareCPOptions($transactionOptions); Yii::debug('Starting mongodb transaction ...', __METHOD__); - if($this->clientSession->mongoSession->isInTransaction()) + if($this->clientSession->GetHasTransaction()) throw new Exception('Nested transaction not supported'); $this->clientSession->db->trigger(Connection::EVENT_START_TRANSACTION); - if($this->mongoSession->db->enableLogging) + if($this->clientSession->db->enableLogging) Yii::beginProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); $this->clientSession->mongoSession->startTransaction($transactionOptions); Yii::debug('MongoDB transaction started.', __METHOD__); @@ -70,7 +70,7 @@ public function start($transactionOptions = []){ public function commit(){ Yii::debug('Committing mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->commitTransaction(); - if($this->mongoSession->db->enableLogging) + if($this->clientSession->db->enableLogging) Yii::endProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); Yii::debug('Commit mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); @@ -83,7 +83,7 @@ public function commit(){ public function rollBack(){ Yii::debug('Rolling back mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->abortTransaction(); - if($this->mongoSession->db->enableLogging) + if($this->clientSession->db->enableLogging) Yii::endProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); Yii::debug('Roll back mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); From 71d0b0135b6c4c7d4e5f5b60aa185e3be8c1b798 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sat, 1 Feb 2020 13:51:49 +0330 Subject: [PATCH 06/82] fix bug --- src/ClientSession.php | 8 ++++++++ src/Transaction.php | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ClientSession.php b/src/ClientSession.php index bbd703dc1..a9baadd36 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -138,6 +138,14 @@ public function getHasTransaction(){ return !empty($this->_transaction); } + /** + * Returns whether a multi-document transaction is in progress + * @return bool + */ + public function getInTransaction(){ + return $this->mongoSession->isInTransaction(); + } + /** * End current session */ diff --git a/src/Transaction.php b/src/Transaction.php index 3c9699f73..c71408cff 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -40,7 +40,7 @@ class Transaction extends \yii\base\BaseObject * can [[commit()]] or [[rollBack()]]. */ public function getIsActive(){ - return $this->clientSession->db->getIsActive() && $this->clientSession->GetHasTransaction(); + return $this->clientSession->db->getIsActive() && $this->clientSession->getInTransaction(); } /** @@ -54,7 +54,7 @@ public function getIsActive(){ public function start($transactionOptions = []){ Command::prepareCPOptions($transactionOptions); Yii::debug('Starting mongodb transaction ...', __METHOD__); - if($this->clientSession->GetHasTransaction()) + if($this->clientSession->getInTransaction()) throw new Exception('Nested transaction not supported'); $this->clientSession->db->trigger(Connection::EVENT_START_TRANSACTION); if($this->clientSession->db->enableLogging) From 1acb69e02d4fa03630ac402a9b2b3e9ca691522b Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Tue, 11 Feb 2020 07:19:25 +0330 Subject: [PATCH 07/82] fix bug in File Collection --- src/file/Collection.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/file/Collection.php b/src/file/Collection.php index 860867c96..1ede82dad 100644 --- a/src/file/Collection.php +++ b/src/file/Collection.php @@ -128,31 +128,30 @@ public function getFileCollection($refresh = false) 'name' => $this->name ]); } - return $this->_fileCollection; } /** * {@inheritdoc} */ - public function drop() + public function drop($execOptions = []) { - return parent::drop() && $this->database->dropCollection($this->getChunkCollection()->name); + return parent::drop($execOptions) && $this->database->dropCollection($this->getChunkCollection()->name,$execOptions); } /** * {@inheritdoc} * @return Cursor cursor for the search results */ - public function find($condition = [], $fields = [], $options = []) + public function find($condition = [], $fields = [], $options = [], $execOptions = []) { - return new Cursor($this, parent::find($condition, $fields, $options)); + return new Cursor($this, parent::find($condition, $fields, $options, $execOptions)); } /** * {@inheritdoc} */ - public function remove($condition = [], $options = []) + public function remove($condition = [], $options = [], $execOptions = []) { $fileCollection = $this->getFileCollection(); $chunkCollection = $this->getChunkCollection(); @@ -166,7 +165,7 @@ public function remove($condition = [], $options = []) $batchSize = 200; $options['batchSize'] = $batchSize; - $cursor = $fileCollection->find($condition, ['_id'], $options); + $cursor = $fileCollection->find($condition, ['_id'], $options, $execOptions); unset($options['limit']); $deleteCount = 0; $deleteCallback = function ($ids) use ($fileCollection, $chunkCollection, $options) { From 4e94ae78a1924c6d2bdaff1a925afe3dc94f0057 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 20 Feb 2020 10:14:55 +0330 Subject: [PATCH 08/82] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d14a042..7b0f3e9cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Yii Framework 2 mongodb extension Change Log 2.1.10 under development ------------------------ -- no changes in this release. +- Feature #294 : now , yii2 support mongodb transaction (ziaratban) 2.1.9 November 19, 2019 From d1e4da8086e3a1b7ee1f0b95e263b32357e90709 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 22 Feb 2020 17:04:45 +0300 Subject: [PATCH 09/82] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b0f3e9cb..3166983f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Yii Framework 2 mongodb extension Change Log 2.1.10 under development ------------------------ -- Feature #294 : now , yii2 support mongodb transaction (ziaratban) +- Enh #294: Add transactions support (ziaratban) 2.1.9 November 19, 2019 From ada4d4e2b323c4b39050f927286983c6d31f16f0 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Wed, 4 Mar 2020 08:59:21 +0330 Subject: [PATCH 10/82] add Throwable in Connection::transaction() --- src/Connection.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Connection.php b/src/Connection.php index 324f85719..0bf1a0292 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -568,6 +568,10 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio if($newClientSession->getTransaction()->getIsActive()) $newClientSession->getTransaction()->rollBack(); throw $e; + } catch (\Throwable $e) { + if($newClientSession->getTransaction()->getIsActive()) + $newClientSession->getTransaction()->rollBack(); + throw $e; } } -} +} \ No newline at end of file From 0ee20d9eb84a998c92def42ba3554fd0b4490186 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Wed, 4 Mar 2020 09:29:55 +0330 Subject: [PATCH 11/82] support OP_ALL , OP_DELETE , OP_INSERT , OP_UPDATE see https://www.yiiframework.com/doc/api/2.0/yii-db-activerecord#transactions()-detail --- src/ActiveRecord.php | 91 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index c720d94b2..2c1c5e3f6 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -208,8 +208,14 @@ public function insert($runValidation = true, $attributes = null) if ($runValidation && !$this->validate($attributes)) { return false; } - $result = $this->insertInternal($attributes); + if(!$this->isTransactional(self::OP_INSERT)) + return $this->insertInternal($attributes); + + $result = null; + static::getDb()->transaction(function()use($attribute,&$result){ + $result = $this->insertInternal($attributes); + }); return $result; } @@ -243,6 +249,75 @@ protected function insertInternal($attributes = null) return true; } + /** + * Saves the changes to this active record into the associated database table. + * + * This method performs the following steps in order: + * + * 1. call [[beforeValidate()]] when `$runValidation` is `true`. If [[beforeValidate()]] + * returns `false`, the rest of the steps will be skipped; + * 2. call [[afterValidate()]] when `$runValidation` is `true`. If validation + * failed, the rest of the steps will be skipped; + * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`, + * the rest of the steps will be skipped; + * 4. save the record into database. If this fails, it will skip the rest of the steps; + * 5. call [[afterSave()]]; + * + * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], + * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_UPDATE]], and [[EVENT_AFTER_UPDATE]] + * will be raised by the corresponding methods. + * + * Only the [[dirtyAttributes|changed attribute values]] will be saved into database. + * + * For example, to update a customer record: + * + * ```php + * $customer = Customer::findOne($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->update(); + * ``` + * + * Note that it is possible the update does not affect any row in the table. + * In this case, this method will return 0. For this reason, you should use the following + * code to check if update() is successful or not: + * + * ```php + * if ($customer->update() !== false) { + * // update successful + * } else { + * // update failed + * } + * ``` + * + * @param bool $runValidation whether to perform validation (calling [[validate()]]) + * before saving the record. Defaults to `true`. If the validation fails, the record + * will not be saved to the database and this method will return `false`. + * @param array $attributeNames list of attributes that need to be saved. Defaults to `null`, + * meaning all attributes that are loaded from DB will be saved. + * @return int|false the number of rows affected, or false if validation fails + * or [[beforeSave()]] stops the updating process. + * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data + * being updated is outdated. + * @throws \Exception|\Throwable in case update failed. + */ + public function update($runValidation = true, $attributeNames = null) + { + if ($runValidation && !$this->validate($attributeNames)) { + Yii::info('Model not updated due to validation error.', __METHOD__); + return false; + } + + if(!$this->isTransactional(self::OP_UPDATE)) + return $this->updateInternal($attributeNames); + + $result = null; + static::getDb()->transaction(function()use($attributeNames,&$result){ + $result = $this->updateInternal($attributeNames); + }); + return $result; + } + /** * @see ActiveRecord::update() * @throws StaleObjectException @@ -308,12 +383,13 @@ protected function updateInternal($attributes = null) */ public function delete() { - $result = false; - if ($this->beforeDelete()) { - $result = $this->deleteInternal(); - $this->afterDelete(); - } + if(!$this->isTransactional(self::OP_DELETE)) + return $this->deleteInternal(); + $result = null; + static::getDb()->transaction(function()use(&$result){ + $result = $this->deleteInternal(); + }); return $result; } @@ -323,6 +399,8 @@ public function delete() */ protected function deleteInternal() { + if(!$this->beforeDelete()) + return false; // we do not check the return value of deleteAll() because it's possible // the record is already deleted in the database and thus the method will return 0 $condition = $this->getOldPrimaryKey(true); @@ -335,6 +413,7 @@ protected function deleteInternal() throw new StaleObjectException('The object being deleted is outdated.'); } $this->setOldAttributes(null); + $this->afterDelete() return $result; } From 50b0bb55d9209151c0e0ce98e5b2e0ea5548e8bd Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Wed, 4 Mar 2020 14:49:14 +0330 Subject: [PATCH 12/82] adding document lock feature --- src/ActiveRecord.php | 20 ++++++++++++++++++++ src/Connection.php | 29 +++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 2c1c5e3f6..e7953eff7 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -9,6 +9,7 @@ use MongoDB\BSON\Binary; use MongoDB\BSON\Type; +use MongoDB\BSON\ObjectId; use Yii; use yii\base\InvalidConfigException; use yii\db\BaseActiveRecord; @@ -490,4 +491,23 @@ private function dumpBsonObject(Type $object) } return ArrayHelper::toArray($object); } + + /** + * Lock a document in a transaction(like `select for update` feature in mysql) + * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions + * @param mixed $id a document id(primary key > _id) + * @param array $options list of options in format: optionName => optionValue. + * @param Connection $db the Mongo connection used to execute the query. + * @return ActiveRecord|array|null the original document, or the modified document when $options['new'] is set. + * Depending on the setting of [[asArray]], the query result may be either an array or an ActiveRecord object. + * Null will be returned if the query results in nothing. + */ + public static function LockDocument($id, $options = [], $db = null){ + ($db ? $db : static::getDb())->transactionReady('lock document'); + return + self::find() + ->where(['_id' => $id]) + ->modify(['_lock' => new ObjectId], $options, $db) + ; + } } diff --git a/src/Connection.php b/src/Connection.php index 0bf1a0292..74cbe7da1 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -487,6 +487,25 @@ public function getInSession(){ return array_key_exists('session',$this->globalExecOptions); } + /** + * check if current connection is in session and transaction + * return bool + */ + public function getInTransaction(){ + return $this->getInSession() && $this->getSession()->getHasTransaction(); + } + + /** + * throw custome error if transaction is not ready in connection + * @param string $operation a custom message to be shown + */ + public function transactionReady($operation){ + if(!$this->getInSession()) + throw new Exception('You can\'t '.$operation.' because current connection is\'t in a session.'); + if(!$this->getSession()->getHasTransaction()) + throw new Exception('You can\'t '.$operation.' because transaction not started in current session.'); + } + /** * return current session * return ClientSession|null @@ -515,10 +534,7 @@ public function startTransaction($transactionOptions = [], $sessionOptions = []) * commit transaction in current session */ public function commitTransaction(){ - if(!$this->getInSession()) - throw new Exception('You can\'t commit transaction because current connection is\'t in a session.'); - if(!$this->getSession()->getHasTransaction()) - throw new Exception('You can\'t commit transaction because transaction not started in current session.'); + $this->transactionReady('commit transaction'); $this->getSession()->transaction->commit(); } @@ -526,10 +542,7 @@ public function commitTransaction(){ * rollback transaction in current session */ public function rollBackTransaction(){ - if(!$this->getInSession()) - throw new Exception('You can\'t roll back transaction because current connection is\'t in a session.'); - if(!$this->getSession()->getHasTransaction()) - throw new Exception('You can\'t roll back transaction because transaction not started in current session.'); + $this->transactionReady('roll back transaction'); $this->getSession()->transaction->rollBack(); } From 293951f175a0e9b9423c173fd288279341061fe2 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Wed, 4 Mar 2020 20:00:17 +0330 Subject: [PATCH 13/82] fix bug --- src/ActiveRecord.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index e7953eff7..423749a19 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -26,6 +26,12 @@ */ abstract class ActiveRecord extends BaseActiveRecord { + /** + * @var string default lock field name in LockDocument() method + * this property can be define by end user + */ + public static $lockField = '_lock'; + /** * Returns the Mongo connection used by this AR class. * By default, the "mongodb" application component is used as the Mongo connection. @@ -507,7 +513,7 @@ public static function LockDocument($id, $options = [], $db = null){ return self::find() ->where(['_id' => $id]) - ->modify(['_lock' => new ObjectId], $options, $db) + ->modify(['$set' => [static::$lockField => new ObjectId]], $options, $db) ; } } From 63fd4a04baa22bb19d4c90857c61d7caba51bfa9 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Wed, 4 Mar 2020 21:03:55 +0330 Subject: [PATCH 14/82] Update ActiveRecord.php --- src/ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 423749a19..7367fd9a8 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -420,7 +420,7 @@ protected function deleteInternal() throw new StaleObjectException('The object being deleted is outdated.'); } $this->setOldAttributes(null); - $this->afterDelete() + $this->afterDelete(); return $result; } From 6e97759c1f0d0917ae088d57e9da2a8cab483349 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Wed, 4 Mar 2020 21:20:17 +0330 Subject: [PATCH 15/82] Update ActiveRecord.php --- src/ActiveRecord.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 7367fd9a8..2aa935800 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -504,16 +504,18 @@ private function dumpBsonObject(Type $object) * @param mixed $id a document id(primary key > _id) * @param array $options list of options in format: optionName => optionValue. * @param Connection $db the Mongo connection used to execute the query. - * @return ActiveRecord|array|null the original document, or the modified document when $options['new'] is set. + * @return ActiveRecord|array|null the modified document. * Depending on the setting of [[asArray]], the query result may be either an array or an ActiveRecord object. * Null will be returned if the query results in nothing. */ public static function LockDocument($id, $options = [], $db = null){ - ($db ? $db : static::getDb())->transactionReady('lock document'); + $db = $db ? $db : static::getDb(); + $db->transactionReady('lock document'); + $options['new'] = true; return self::find() ->where(['_id' => $id]) ->modify(['$set' => [static::$lockField => new ObjectId]], $options, $db) ; } -} +} \ No newline at end of file From 01f4ad2b4017c8cd8dd3b8b00f0afa59e3765fab Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 5 Mar 2020 13:43:23 +0330 Subject: [PATCH 16/82] Update ActiveRecord.php --- src/ActiveRecord.php | 66 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 2aa935800..eff49b46e 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -26,6 +26,27 @@ */ abstract class ActiveRecord extends BaseActiveRecord { + /** + * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. + */ + const OP_INSERT = 0x01; + + /** + * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. + */ + const OP_UPDATE = 0x02; + + /** + * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. + */ + const OP_DELETE = 0x04; + + /** + * All three operations: insert, update, delete. + * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE. + */ + const OP_ALL = 0x07; + /** * @var string default lock field name in LockDocument() method * this property can be define by end user @@ -518,4 +539,49 @@ public static function LockDocument($id, $options = [], $db = null){ ->modify(['$set' => [static::$lockField => new ObjectId]], $options, $db) ; } + + /** + * Declares which DB operations should be performed within a transaction in different scenarios. + * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]], + * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively. + * By default, these methods are NOT enclosed in a DB transaction. + * + * In some scenarios, to ensure data consistency, you may want to enclose some or all of them + * in transactions. You can do so by overriding this method and returning the operations + * that need to be transactional. For example, + * + * ```php + * return [ + * 'admin' => self::OP_INSERT, + * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, + * // the above is equivalent to the following: + * // 'api' => self::OP_ALL, + * + * ]; + * ``` + * + * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]]) + * should be done in a transaction; and in the "api" scenario, all the operations should be done + * in a transaction. + * + * @return array the declarations of transactional operations. The array keys are scenarios names, + * and the array values are the corresponding transaction operations. + */ + public function transactions() + { + return []; + } + + /** + * Returns a value indicating whether the specified operation is transactional in the current [[$scenario]]. + * @param int $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. + * @return bool whether the specified operation is transactional in the current [[scenario]]. + */ + public function isTransactional($operation) + { + $scenario = $this->getScenario(); + $transactions = $this->transactions(); + + return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation); + } } \ No newline at end of file From 5c7ff07fa164004447c9d52d247ef628e565681d Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Mon, 9 Mar 2020 11:04:41 +0330 Subject: [PATCH 17/82] support writeConcern number parameter [ 'writeConcern' => 1 ], --- src/ClientSession.php | 2 +- src/Command.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ClientSession.php b/src/ClientSession.php index a9baadd36..cb566e918 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -76,7 +76,7 @@ public static function prepareOptions(&$options){ #convert writeConcern if(array_key_exists('writeConcern',$options['defaultTransactionOptions'])){ - if(is_string($options['defaultTransactionOptions']['writeConcern'])) + if(is_string($options['defaultTransactionOptions']['writeConcern']) || is_int($options['defaultTransactionOptions']['writeConcern'])) $options['defaultTransactionOptions']['writeConcern'] = new WriteConcern($options['defaultTransactionOptions']['writeConcern']); else if(is_array($options['defaultTransactionOptions']['writeConcern'])) $options['defaultTransactionOptions']['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($options['defaultTransactionOptions']['writeConcern']); diff --git a/src/Command.php b/src/Command.php index 9b27c9489..47b85a0f2 100644 --- a/src/Command.php +++ b/src/Command.php @@ -106,7 +106,7 @@ public static function prepareCPOptions(&$options){ #convert writeConcern option if(array_key_exists('writeConcern', $options)){ - if(is_string($options['writeConcern'])) + if(is_string($options['writeConcern']) || is_int($options['writeConcern'])) $options['writeConcern'] = new WriteConcern($options['writeConcern']); elseif(is_array($options['writeConcern'])) $options['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($options['writeConcern']); From e21a11b9ce6a76d4365afb22a0721ae84c40dc41 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Mon, 9 Mar 2020 14:52:14 +0330 Subject: [PATCH 18/82] adding stubborn feature for lock a document locking a document in stubborn mode on a transaction(like `select for update` feature in mysql) --- src/ActiveRecord.php | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index eff49b46e..9babecfb7 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -540,6 +540,65 @@ public static function LockDocument($id, $options = [], $db = null){ ; } + /** + * locking a document in stubborn mode on a transaction(like `select for update` feature in mysql) + * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions + * notice : before call this method you must save last mongodb client session from db connection + * notice : this lock occurred in a new session and transaction + * @param mixed $id a document id(primary key > _id) + * @param array $options list of options in format: + * [ + * 'sessionOptions' => [], #new session options. see $sessionOptions in ClientSession::start() + * 'transactionOptions' => [], #new transaction options. see $transactionOptions in Transaction::start() + * 'modifyOptions' => [], #see $options in ActiveQuery::modify() + * 'sleep' => 1000000, #time in microseconds for wait.default is one second + * 'tiredAfter' => 0, #maximum count of retry. throw write conflict error after reached this value. zero default is unlimited. + * ] + * @param Connection $db the Mongo connection used to execute the query. + * @return ActiveRecord|array|null the modified document. + * Depending on the setting of [[asArray]], the query result may be either an array or an ActiveRecord object. + * Null will be returned if the query results in nothing. + * Throw write conflict error after reached $options['tiredAfter'] value + */ + public static function StubbornLockDocument($id, $options = [], $db = null){ + + $db = $db ? $db : static::getDb(); + + $options = array_replace_recursive([ + 'sessionOptions' => [], + 'transactionOptions' => [], + 'modifyOptions' => [], + 'sleep' => 1000000, #in microseconds + 'tiredAfter' => 0, + ],$options); + + $options['modifyOptions']['new'] = true; + + #create new session for stubbornness + $newClientSession = $db->startSession($options['sessionOptions']); + $db->withSession($newClientSession); + + #start stubborn + $tiredCounter = 0; + StartStubborn: + $newClientSession->transaction->start($options['transactionOptions']); + try{ + $doc = + self::find() + ->where(['_id' => $id]) + ->modify(['$set' => [static::$lockField => new ObjectId]], $options['modifyOptions'], $db) + ; + return $doc; + }catch(\Exception $e){ + $newClientSession->transaction->rollBack(); + $tiredCounter++; + if($options['tiredAfter'] !== 0 && $tiredCounter === $options['tiredAfter']) + throw $e; + usleep($options['sleep']); + goto StartStubborn; + } + } + /** * Declares which DB operations should be performed within a transaction in different scenarios. * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]], From 278989e1b531710d767db400ca7ee9e9915964bc Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Mon, 6 Apr 2020 18:13:01 +0430 Subject: [PATCH 19/82] save last session in Connection::Transaction() --- src/Connection.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Connection.php b/src/Connection.php index 74cbe7da1..e93584ee9 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -569,7 +569,10 @@ public function withSession($clientSession){ * return $this */ public function transaction(callable $actions, $transactionOptions = [], $sessionOptions = []){ + #save last session for return + $lastSession = isset($this->globalExecOptions['session']) ? $this->globalExecOptions['session'] : null; $newClientSession = $this->startTransaction($transactionOptions, $sessionOptions); + $success = false; try { $result = call_user_func($actions, $newClientSession); if($newClientSession->getTransaction()->getIsActive()) @@ -577,14 +580,12 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio $newClientSession->getTransaction()->rollBack(); else $newClientSession->getTransaction()->commit(); - } catch (\Exception $e){ - if($newClientSession->getTransaction()->getIsActive()) - $newClientSession->getTransaction()->rollBack(); - throw $e; - } catch (\Throwable $e) { - if($newClientSession->getTransaction()->getIsActive()) + $success = true; + } finally { + if(!$success && $newClientSession->getTransaction()->getIsActive()) $newClientSession->getTransaction()->rollBack(); - throw $e; + #return last session + $this->withSession($lastSession); } } } \ No newline at end of file From 88e656c4488d3a0533bf36e27266f5b31e2a515d Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Tue, 7 Apr 2020 07:25:18 +0430 Subject: [PATCH 20/82] Update Connection.php --- src/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Connection.php b/src/Connection.php index e93584ee9..5b22b7bfb 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -466,7 +466,7 @@ public function execOptions($execOptions){ if(empty($execOptions)) $this->globalExecOptions = []; else - $this->globalExecOptions = array_merge_recursive($this->globalExecOptions, $execOptions); + $this->globalExecOptions = array_replace_recursive($this->globalExecOptions, $execOptions); return $this; } From ab96f623b51039e54102171904c0589e15894a47 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 9 Apr 2020 10:51:09 +0430 Subject: [PATCH 21/82] updating documents --- src/ActiveRecord.php | 10 ++++------ src/Connection.php | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 9babecfb7..bd3e3cb6f 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -525,9 +525,8 @@ private function dumpBsonObject(Type $object) * @param mixed $id a document id(primary key > _id) * @param array $options list of options in format: optionName => optionValue. * @param Connection $db the Mongo connection used to execute the query. - * @return ActiveRecord|array|null the modified document. - * Depending on the setting of [[asArray]], the query result may be either an array or an ActiveRecord object. - * Null will be returned if the query results in nothing. + * @return ActiveRecord|null the modified document. + * Returns instance of ActiveRecord. Null will be returned if the query results in nothing. */ public static function LockDocument($id, $options = [], $db = null){ $db = $db ? $db : static::getDb(); @@ -555,9 +554,8 @@ public static function LockDocument($id, $options = [], $db = null){ * 'tiredAfter' => 0, #maximum count of retry. throw write conflict error after reached this value. zero default is unlimited. * ] * @param Connection $db the Mongo connection used to execute the query. - * @return ActiveRecord|array|null the modified document. - * Depending on the setting of [[asArray]], the query result may be either an array or an ActiveRecord object. - * Null will be returned if the query results in nothing. + * @return ActiveRecord|null the modified document. + * Returns instance of ActiveRecord. Null will be returned if the query results in nothing. * Throw write conflict error after reached $options['tiredAfter'] value */ public static function StubbornLockDocument($id, $options = [], $db = null){ diff --git a/src/Connection.php b/src/Connection.php index 5b22b7bfb..26e5fba7e 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -566,7 +566,6 @@ public function withSession($clientSession){ * if $actions return false then transaction rolled back. * @param array $transactionOptions see doc of Transaction::start() * @param array $sessionOptions see doc of ClientSession::start() - * return $this */ public function transaction(callable $actions, $transactionOptions = [], $sessionOptions = []){ #save last session for return From be17b903996bd922d76db578bc07b02939521af2 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 9 Apr 2020 21:11:00 +0430 Subject: [PATCH 22/82] Update Connection.php --- src/Connection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Connection.php b/src/Connection.php index 26e5fba7e..85fc921e2 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -568,8 +568,8 @@ public function withSession($clientSession){ * @param array $sessionOptions see doc of ClientSession::start() */ public function transaction(callable $actions, $transactionOptions = [], $sessionOptions = []){ - #save last session for return - $lastSession = isset($this->globalExecOptions['session']) ? $this->globalExecOptions['session'] : null; + #save last mongo session for return + $lastSession = $this->getSession(); $newClientSession = $this->startTransaction($transactionOptions, $sessionOptions); $success = false; try { @@ -583,7 +583,7 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio } finally { if(!$success && $newClientSession->getTransaction()->getIsActive()) $newClientSession->getTransaction()->rollBack(); - #return last session + #return last mongo session $this->withSession($lastSession); } } From a084b8d48cadcc8216c02471aa73719c0d2e7afc Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 9 Apr 2020 22:21:29 +0430 Subject: [PATCH 23/82] rename Connection::withSession() to Connection::setSession() based on setter and getter standard --- src/ActiveRecord.php | 2 +- src/Connection.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index bd3e3cb6f..ddff603a1 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -574,7 +574,7 @@ public static function StubbornLockDocument($id, $options = [], $db = null){ #create new session for stubbornness $newClientSession = $db->startSession($options['sessionOptions']); - $db->withSession($newClientSession); + $db->setSession($newClientSession); #start stubborn $tiredCounter = 0; diff --git a/src/Connection.php b/src/Connection.php index 85fc921e2..1001ebe8e 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -526,7 +526,7 @@ public function getSession(){ public function startTransaction($transactionOptions = [], $sessionOptions = []){ $newClientSession = $this->startSession($sessionOptions); $newClientSession->getTransaction()->start($transactionOptions); - $this->withSession($newClientSession); + $this->setSession($newClientSession); return $newClientSession; } @@ -551,7 +551,7 @@ public function rollBackTransaction(){ * @param ClientSession|null $clientSession new instance of ClientSession for replace * return $this */ - public function withSession($clientSession){ + public function setSession($clientSession){ #drop session if(empty($clientSession)) unset($this->globalExecOptions['session']); @@ -584,7 +584,7 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio if(!$success && $newClientSession->getTransaction()->getIsActive()) $newClientSession->getTransaction()->rollBack(); #return last mongo session - $this->withSession($lastSession); + $this->setSession($lastSession); } } } \ No newline at end of file From 6463da34127ec0e61c27751e5e99904746ac2763 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sat, 11 Apr 2020 01:18:27 +0430 Subject: [PATCH 24/82] noTransaction feature run your mongodb command out of session and transaction. --- src/Connection.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Connection.php b/src/Connection.php index 1001ebe8e..918e4f2c5 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -587,4 +587,15 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio $this->setSession($lastSession); } } + + /** + * run your mongodb command out of session and transaction. returns last mongodb session after end of codes. + * @param callable $actions your block of code must be run out of session and transaction + */ + public function noTransaction(callable $actions){ + $lastSession = $this->session; + $this->session = null; + $actions(); + $this->session = $lastSession; + } } \ No newline at end of file From ea65e02137d12428ee9935be4921f90da1f3edec Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sat, 11 Apr 2020 01:24:32 +0430 Subject: [PATCH 25/82] adding 'return' to Connection::noTransaction() --- src/Connection.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Connection.php b/src/Connection.php index 918e4f2c5..c3312c7a0 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -591,11 +591,13 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio /** * run your mongodb command out of session and transaction. returns last mongodb session after end of codes. * @param callable $actions your block of code must be run out of session and transaction + * return result of $actions() */ public function noTransaction(callable $actions){ $lastSession = $this->session; $this->session = null; - $actions(); + $result = $actions(); $this->session = $lastSession; + return $result; } } \ No newline at end of file From e67cebb80236fa31b1788c9ac3236fbc2881dcc3 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sat, 11 Apr 2020 17:54:19 +0430 Subject: [PATCH 26/82] add exception handling for a safe return --- src/Connection.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Connection.php b/src/Connection.php index c3312c7a0..99276efb2 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -589,15 +589,19 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio } /** - * run your mongodb command out of session and transaction. returns last mongodb session after end of codes. + * run your mongodb command out of session and transaction. + * returns last mongodb session to current session after end of codes. * @param callable $actions your block of code must be run out of session and transaction * return result of $actions() */ public function noTransaction(callable $actions){ - $lastSession = $this->session; - $this->session = null; - $result = $actions(); - $this->session = $lastSession; + $lastSession = $this->getSession(); + $this->setSession(null); + try { + $result = $actions(); + } finally { + $this->setSession($lastSession); + } return $result; } } \ No newline at end of file From 782a9d542f7c46fcb11b953b49baa8927a3ec50f Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 2 Jul 2020 17:03:11 +0430 Subject: [PATCH 27/82] match with enableLogging and enableProfiling --- src/Transaction.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Transaction.php b/src/Transaction.php index c71408cff..d095b294b 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -53,14 +53,16 @@ public function getIsActive(){ */ public function start($transactionOptions = []){ Command::prepareCPOptions($transactionOptions); - Yii::debug('Starting mongodb transaction ...', __METHOD__); + if($this->clientSession->db->enableLogging) + Yii::debug('Starting mongodb transaction ...', __METHOD__); if($this->clientSession->getInTransaction()) throw new Exception('Nested transaction not supported'); $this->clientSession->db->trigger(Connection::EVENT_START_TRANSACTION); - if($this->clientSession->db->enableLogging) + if($this->clientSession->db->enableProfiling) Yii::beginProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); $this->clientSession->mongoSession->startTransaction($transactionOptions); - Yii::debug('MongoDB transaction started.', __METHOD__); + if($this->clientSession->db->enableLogging) + Yii::debug('MongoDB transaction started.', __METHOD__); } /** @@ -68,11 +70,13 @@ public function start($transactionOptions = []){ * @see https://www.php.net/manual/en/mongodb-driver-session.committransaction.php */ public function commit(){ - Yii::debug('Committing mongodb transaction ...', __METHOD__); - $this->clientSession->mongoSession->commitTransaction(); if($this->clientSession->db->enableLogging) + Yii::debug('Committing mongodb transaction ...', __METHOD__); + $this->clientSession->mongoSession->commitTransaction(); + if($this->clientSession->db->enableProfiling) Yii::endProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); - Yii::debug('Commit mongodb transaction.', __METHOD__); + if($this->clientSession->db->enableLogging) + Yii::debug('Commit mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); } @@ -81,11 +85,13 @@ public function commit(){ * @see https://www.php.net/manual/en/mongodb-driver-session.aborttransaction.php */ public function rollBack(){ - Yii::debug('Rolling back mongodb transaction ...', __METHOD__); - $this->clientSession->mongoSession->abortTransaction(); if($this->clientSession->db->enableLogging) + Yii::debug('Rolling back mongodb transaction ...', __METHOD__); + $this->clientSession->mongoSession->abortTransaction(); + if($this->clientSession->db->enableProfiling) Yii::endProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); - Yii::debug('Roll back mongodb transaction.', __METHOD__); + if($this->clientSession->db->enableLogging) + Yii::debug('Roll back mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); } } \ No newline at end of file From 96aa4ec81bbd336869b38d50c986bf8a8c0267e0 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 2 Jul 2020 17:21:51 +0430 Subject: [PATCH 28/82] cleaning --- src/Transaction.php | 57 +++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/src/Transaction.php b/src/Transaction.php index d095b294b..5c9b1dcf5 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -34,6 +34,36 @@ class Transaction extends \yii\base\BaseObject */ public $clientSession; + /** + * Set debug message if `enableLogging` property is enable in yii\mongodb\Connection + * @var string $message please see $this->yiiDebug() + * @var string $category please see $this->yiiDebug() + */ + protected function yiiDebug($message, $category = 'mongodb'){ + if($this->clientSession->db->enableLogging) + $this->yiiDebug($message,$category); + } + + /** + * Begin profile if `enableProfiling` property is enable in yii\mongodb\Connection + * @var string $token please see $this->yiiBeginProfile() + * @var string $category please see $this->yiiBeginProfile() + */ + protected function yiiBeginProfile($token, $category = 'mongodb'){ + if($this->clientSession->db->enableProfiling) + $this->yiiBeginProfile($token,$category); + } + + /** + * End profile if `enableProfiling` property is enable in yii\mongodb\Connection + * @var string $token please see $this->yiiEndProfile() + * @var string $category please see $this->yiiEndProfile() + */ + protected function yiiEndProfile($token, $category = 'mongodb'){ + if($this->clientSession->db->enableProfiling) + $this->yiiEndProfile($token,$category); + } + /** * Returns a value indicating whether this transaction is active. * @return bool whether this transaction is active. Only an active transaction @@ -53,16 +83,13 @@ public function getIsActive(){ */ public function start($transactionOptions = []){ Command::prepareCPOptions($transactionOptions); - if($this->clientSession->db->enableLogging) - Yii::debug('Starting mongodb transaction ...', __METHOD__); + $this->yiiDebug('Starting mongodb transaction ...', __METHOD__); if($this->clientSession->getInTransaction()) throw new Exception('Nested transaction not supported'); $this->clientSession->db->trigger(Connection::EVENT_START_TRANSACTION); - if($this->clientSession->db->enableProfiling) - Yii::beginProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); + $this->yiiBeginProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); $this->clientSession->mongoSession->startTransaction($transactionOptions); - if($this->clientSession->db->enableLogging) - Yii::debug('MongoDB transaction started.', __METHOD__); + $this->yiiDebug('MongoDB transaction started.', __METHOD__); } /** @@ -70,13 +97,10 @@ public function start($transactionOptions = []){ * @see https://www.php.net/manual/en/mongodb-driver-session.committransaction.php */ public function commit(){ - if($this->clientSession->db->enableLogging) - Yii::debug('Committing mongodb transaction ...', __METHOD__); + $this->yiiDebug('Committing mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->commitTransaction(); - if($this->clientSession->db->enableProfiling) - Yii::endProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); - if($this->clientSession->db->enableLogging) - Yii::debug('Commit mongodb transaction.', __METHOD__); + $this->yiiEndProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); + $this->yiiDebug('Commit mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); } @@ -85,13 +109,10 @@ public function commit(){ * @see https://www.php.net/manual/en/mongodb-driver-session.aborttransaction.php */ public function rollBack(){ - if($this->clientSession->db->enableLogging) - Yii::debug('Rolling back mongodb transaction ...', __METHOD__); + $this->yiiDebug('Rolling back mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->abortTransaction(); - if($this->clientSession->db->enableProfiling) - Yii::endProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); - if($this->clientSession->db->enableLogging) - Yii::debug('Roll back mongodb transaction.', __METHOD__); + $this->yiiEndProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); + $this->yiiDebug('Roll back mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); } } \ No newline at end of file From 0193317599941ab812b2d5eaa3305d62dbe3fcbd Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 9 Jul 2020 06:55:01 +0430 Subject: [PATCH 29/82] fix bug --- src/Transaction.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Transaction.php b/src/Transaction.php index 5c9b1dcf5..646ccd686 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -41,7 +41,7 @@ class Transaction extends \yii\base\BaseObject */ protected function yiiDebug($message, $category = 'mongodb'){ if($this->clientSession->db->enableLogging) - $this->yiiDebug($message,$category); + yii::debug($message,$category); } /** @@ -51,7 +51,7 @@ protected function yiiDebug($message, $category = 'mongodb'){ */ protected function yiiBeginProfile($token, $category = 'mongodb'){ if($this->clientSession->db->enableProfiling) - $this->yiiBeginProfile($token,$category); + yii::beginProfile($token,$category); } /** @@ -61,7 +61,7 @@ protected function yiiBeginProfile($token, $category = 'mongodb'){ */ protected function yiiEndProfile($token, $category = 'mongodb'){ if($this->clientSession->db->enableProfiling) - $this->yiiEndProfile($token,$category); + yii::endProfile($token,$category); } /** From 3185c2615f65cc86ad84e4549c318dc7a0a063d1 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 1 Oct 2020 23:18:29 +0330 Subject: [PATCH 30/82] fix bug https://github.com/yiisoft/yii2-mongodb/pull/314 --- src/ClientSession.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ClientSession.php b/src/ClientSession.php index cb566e918..058fd527c 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -110,13 +110,15 @@ public function getId(){ */ public static function start($db, $sessionOptions = []){ self::prepareOptions($sessionOptions); - Yii::debug('Starting mongodb session ...', __METHOD__); + if($db->enableProfiling) + Yii::debug('Starting mongodb session ...', __METHOD__); $db->trigger(Connection::EVENT_START_SESSION); $newSession = new self([ 'db' => $db, 'mongoSession' => $db->manager->startSession($sessionOptions), ]); - Yii::debug('MongoDB session started.', __METHOD__); + if($db->enableProfiling) + Yii::debug('MongoDB session started.', __METHOD__); return $newSession; } From 22d8c6f4e99621867342c15921b7799e104128e2 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Fri, 2 Oct 2020 13:16:08 +0330 Subject: [PATCH 31/82] fix bug --- src/ClientSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ClientSession.php b/src/ClientSession.php index 058fd527c..f6fd1984a 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -153,6 +153,6 @@ public function getInTransaction(){ */ public function end(){ $this->mongoSession->endSession(); - $db->trigger(Connection::EVENT_END_SESSION); + $this->db->trigger(Connection::EVENT_END_SESSION); } } From 4e777d94a134c3e79080975ce83595e3fe4ec9d6 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Fri, 2 Oct 2020 22:57:44 +0330 Subject: [PATCH 32/82] Optimization and better understanding and fix bugs --- src/ActiveRecord.php | 27 +++++++++++----------- src/ClientSession.php | 8 ------- src/Command.php | 2 +- src/Connection.php | 54 +++++++++++++++++++++++++++---------------- src/Transaction.php | 12 ++++++++++ 5 files changed, 61 insertions(+), 42 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index ddff603a1..19eeb7ebd 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -533,7 +533,7 @@ public static function LockDocument($id, $options = [], $db = null){ $db->transactionReady('lock document'); $options['new'] = true; return - self::find() + static::find() ->where(['_id' => $id]) ->modify(['$set' => [static::$lockField => new ObjectId]], $options, $db) ; @@ -547,11 +547,11 @@ public static function LockDocument($id, $options = [], $db = null){ * @param mixed $id a document id(primary key > _id) * @param array $options list of options in format: * [ - * 'sessionOptions' => [], #new session options. see $sessionOptions in ClientSession::start() - * 'transactionOptions' => [], #new transaction options. see $transactionOptions in Transaction::start() - * 'modifyOptions' => [], #see $options in ActiveQuery::modify() - * 'sleep' => 1000000, #time in microseconds for wait.default is one second - * 'tiredAfter' => 0, #maximum count of retry. throw write conflict error after reached this value. zero default is unlimited. + * 'mySession' => false, #custom session instance of ClientSession. + * 'transactionOptions' => [], #new transaction options. see $transactionOptions in Transaction::start() + * 'modifyOptions' => [], #see $options in ActiveQuery::modify() + * 'sleep' => 1000000, #time in microseconds for wait. default is one second. + * 'tiredAfter' => 0, #maximum count of retry. throw write conflict error after reached this value. zero default is unlimited. * ] * @param Connection $db the Mongo connection used to execute the query. * @return ActiveRecord|null the modified document. @@ -563,7 +563,7 @@ public static function StubbornLockDocument($id, $options = [], $db = null){ $db = $db ? $db : static::getDb(); $options = array_replace_recursive([ - 'sessionOptions' => [], + 'mySession' => false, 'transactionOptions' => [], 'modifyOptions' => [], 'sleep' => 1000000, #in microseconds @@ -572,23 +572,24 @@ public static function StubbornLockDocument($id, $options = [], $db = null){ $options['modifyOptions']['new'] = true; - #create new session for stubbornness - $newClientSession = $db->startSession($options['sessionOptions']); - $db->setSession($newClientSession); + $session = $options['mySession'] ? $options['mySession'] : $db->startSession([],true); + + if($session->getInTransaction()) + throw new Exception('You can\'t use stubborn lock feature because current connection is in a transaction.'); #start stubborn $tiredCounter = 0; StartStubborn: - $newClientSession->transaction->start($options['transactionOptions']); + $session->transaction->start($options['transactionOptions']); try{ $doc = - self::find() + static::find() ->where(['_id' => $id]) ->modify(['$set' => [static::$lockField => new ObjectId]], $options['modifyOptions'], $db) ; return $doc; }catch(\Exception $e){ - $newClientSession->transaction->rollBack(); + $session->transaction->rollBack(); $tiredCounter++; if($options['tiredAfter'] !== 0 && $tiredCounter === $options['tiredAfter']) throw $e; diff --git a/src/ClientSession.php b/src/ClientSession.php index f6fd1984a..b81ff8eb2 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -132,14 +132,6 @@ public function getTransaction(){ return $this->_transaction; } - /** - * current session has a transaction? - * @return bool return true if transaction exists otherwise return false - */ - public function getHasTransaction(){ - return !empty($this->_transaction); - } - /** * Returns whether a multi-document transaction is in progress * @return bool diff --git a/src/Command.php b/src/Command.php index 47b85a0f2..b34e0c255 100644 --- a/src/Command.php +++ b/src/Command.php @@ -159,12 +159,12 @@ public function execute($execOptions = []) * Execute commands batch (bulk). * @param string $collectionName collection name. * @param array $options batch options. + * @param array $execOptions options for executeBulkWrite * @return array array of 2 elements: * * - 'insertedIds' - contains inserted IDs. * - 'result' - [[\MongoDB\Driver\WriteResult]] instance. * - * @param array $execOptions options for executeBulkWrite * @see https://www.php.net/manual/en/mongodb-driver-server.executebulkwrite.php#refsect1-mongodb-driver-server.executebulkwrite-parameters * @throws Exception on failure. * @throws InvalidConfigException on invalid [[document]] format. diff --git a/src/Connection.php b/src/Connection.php index 99276efb2..bd45ee094 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -471,11 +471,30 @@ public function execOptions($execOptions){ } /** - * start new session for current connection + * Ends the previous session and starts new session for current connection. * @param array $sessionOptions see doc of ClientSession::start() + * @param array $skip when set to true , no creates new session if exists. * return ClientSession */ - public function startSession($sessionOptions = []){ + public function startSession($sessionOptions = [], $skip = false){ + + if($this->getInSession()){ + if($skip) + return $this->getSession(); + $this->getSession()->end(); + } + + $newSession = ClientSession::start($this, $sessionOptions); + $this->setSession($newSession); + return $newSession; + } + + /** + * only starts new session for current connection and that session does not sets for current connection. + * @param array $sessionOptions see doc of ClientSession::start() + * return ClientSession + */ + public function newSession($sessionOptions = []){ return ClientSession::start($this, $sessionOptions); } @@ -492,7 +511,7 @@ public function getInSession(){ * return bool */ public function getInTransaction(){ - return $this->getInSession() && $this->getSession()->getHasTransaction(); + return $this->getInSession() && $this->getSession()->getInTransaction(); } /** @@ -502,7 +521,7 @@ public function getInTransaction(){ public function transactionReady($operation){ if(!$this->getInSession()) throw new Exception('You can\'t '.$operation.' because current connection is\'t in a session.'); - if(!$this->getSession()->getHasTransaction()) + if(!$this->getSession()->getInTransaction()) throw new Exception('You can\'t '.$operation.' because transaction not started in current session.'); } @@ -516,7 +535,7 @@ public function getSession(){ /** * start transaction with three step : - * - start new session + * - starts new session if has not started * - start transaction of new session * - set new session to current connection * @param array $transactionOptions see doc of Transaction::start() @@ -524,10 +543,9 @@ public function getSession(){ * return ClientSession */ public function startTransaction($transactionOptions = [], $sessionOptions = []){ - $newClientSession = $this->startSession($sessionOptions); - $newClientSession->getTransaction()->start($transactionOptions); - $this->setSession($newClientSession); - return $newClientSession; + $session = $this->startSession($sessionOptions,true); + $session->getTransaction()->start($transactionOptions); + return $session; } /** @@ -568,23 +586,19 @@ public function setSession($clientSession){ * @param array $sessionOptions see doc of ClientSession::start() */ public function transaction(callable $actions, $transactionOptions = [], $sessionOptions = []){ - #save last mongo session for return - $lastSession = $this->getSession(); - $newClientSession = $this->startTransaction($transactionOptions, $sessionOptions); + $session = $this->startTransaction($transactionOptions, $sessionOptions); $success = false; try { - $result = call_user_func($actions, $newClientSession); - if($newClientSession->getTransaction()->getIsActive()) + $result = call_user_func($actions, $session); + if($session->getTransaction()->getIsActive()) if($result === false) - $newClientSession->getTransaction()->rollBack(); + $session->getTransaction()->rollBack(); else - $newClientSession->getTransaction()->commit(); + $session->getTransaction()->commit(); $success = true; } finally { - if(!$success && $newClientSession->getTransaction()->getIsActive()) - $newClientSession->getTransaction()->rollBack(); - #return last mongo session - $this->setSession($lastSession); + if(!$success && $session->getTransaction()->getIsActive()) + $session->getTransaction()->rollBack(); } } diff --git a/src/Transaction.php b/src/Transaction.php index 646ccd686..6b2d23ff1 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -28,6 +28,11 @@ class Transaction extends \yii\base\BaseObject { + const STATE_NONE = 'none'; + const STATE_STARTING = 'starting'; + const STATE_ABORTED = 'aborted'; + const STATE_COMMITTED = 'committed'; + /** * @var MongoDB\Driver\Session class represents a client session and Commands, queries, and write operations may then be associated the session. * @see https://www.php.net/manual/en/class.mongodb-driver-session.php @@ -64,6 +69,13 @@ protected function yiiEndProfile($token, $category = 'mongodb'){ yii::endProfile($token,$category); } + /** + * Returns the transaction state. + */ + public function getState(){ + return $this->clientSession->mongoSession->getTransactionState(); + } + /** * Returns a value indicating whether this transaction is active. * @return bool whether this transaction is active. Only an active transaction From 1a01b197e13b4574527b73fa1483051d73cb61ac Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 20:10:58 +0330 Subject: [PATCH 33/82] Update .travis.yml --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b2b2ec23f..802689b9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ services: addons: apt: sources: - - mongodb-3.0-precise + - mongodb-4.0-precise packages: - mongodb-org-server - mongodb-org-shell @@ -39,6 +39,11 @@ cache: - $HOME/.composer/cache install: + - sudo echo "replication:replSetName: yii2rs" >> /etc/mongodb.conf + - sudo service mongodb restart + - sleep 20 + - mongo --eval 'rs.initiate()' + - sleep 15 - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version - export PATH="$HOME/.composer/vendor/bin:$PATH" @@ -58,4 +63,4 @@ after_script: if [ $TRAVIS_PHP_VERSION = '5.6' ]; then travis_retry wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover - fi + fi \ No newline at end of file From cf8d0222debcecd96b1167f6b7eb89937363d1c4 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 20:14:32 +0330 Subject: [PATCH 34/82] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 802689b9f..7008c0ebf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ cache: - $HOME/.composer/cache install: - - sudo echo "replication:replSetName: yii2rs" >> /etc/mongodb.conf + - sudo echo "replication:replSetName: yii2rs" > /etc/mongodb.conf - sudo service mongodb restart - sleep 20 - mongo --eval 'rs.initiate()' From 260daaf07915535f2153582a587767420ff244b9 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 20:20:43 +0330 Subject: [PATCH 35/82] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7008c0ebf..c4ee35115 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ cache: - $HOME/.composer/cache install: - - sudo echo "replication:replSetName: yii2rs" > /etc/mongodb.conf + - sudo echo 'replication:replSetName: yii2rs' >> /etc/mongod.conf - sudo service mongodb restart - sleep 20 - mongo --eval 'rs.initiate()' From 8f8c18104c37f97a1c8ff6953f9254f942bef8ce Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 20:28:54 +0330 Subject: [PATCH 36/82] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c4ee35115..f5f2dd1d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,8 +39,8 @@ cache: - $HOME/.composer/cache install: - - sudo echo 'replication:replSetName: yii2rs' >> /etc/mongod.conf - - sudo service mongodb restart + - sudo service mongodb stop + - mongo --replSet "rs0" - sleep 20 - mongo --eval 'rs.initiate()' - sleep 15 From 19232894a31dcdb53d6d894436a361129c180e20 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 20:34:10 +0330 Subject: [PATCH 37/82] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f5f2dd1d6..d1f70d5cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ cache: - $HOME/.composer/cache install: - - sudo service mongodb stop + - sudo service mongod stop - mongo --replSet "rs0" - sleep 20 - mongo --eval 'rs.initiate()' From fa9015a58f729af1f9723010d927c467a2b341b4 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 20:51:36 +0330 Subject: [PATCH 38/82] Update .travis.yml --- .travis.yml | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index d1f70d5cd..b1e727d92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ + language: php matrix: @@ -19,31 +20,26 @@ matrix: - php: "5.4" dist: trusty -services: - - mongodb - -addons: - apt: - sources: - - mongodb-4.0-precise - packages: - - mongodb-org-server - - mongodb-org-shell - # faster builds on new travis setup not using sudo sudo: false +env: + global: + - MONGODB_VERSION=2.6.10 + # cache vendor dirs cache: directories: - $HOME/.composer/cache install: - - sudo service mongod stop - - mongo --replSet "rs0" - - sleep 20 + - wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-$MONGODB_VERSION.tgz + - tar xfz mongodb-linux-x86_64-$MONGODB_VERSION.tgz + - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH + - mkdir -p data/db + - mongod --dbpath=data/db --replSet "rs0" & + - sleep 3 - mongo --eval 'rs.initiate()' - - sleep 15 - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version - export PATH="$HOME/.composer/vendor/bin:$PATH" From 9b9e8c0bb6c7717d21e97a8bb54f381151202c71 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 20:55:19 +0330 Subject: [PATCH 39/82] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b1e727d92..64aa0b68a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ install: - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - mongod --dbpath=data/db --replSet "rs0" & - - sleep 3 + - sleep 10 - mongo --eval 'rs.initiate()' - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version From ec360aa79a977a4834fd61de73ca5011e6024ce6 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 20:56:56 +0330 Subject: [PATCH 40/82] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 64aa0b68a..7e1651896 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ sudo: false env: global: - - MONGODB_VERSION=2.6.10 + - MONGODB_VERSION=4.0.0 # cache vendor dirs cache: From 5102f28c0a1c0409c05a8ce6400c46c13222e8e2 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 21:00:27 +0330 Subject: [PATCH 41/82] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7e1651896..529df5912 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ install: - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - mongod --dbpath=data/db --replSet "rs0" & - - sleep 10 + - sleep 30 - mongo --eval 'rs.initiate()' - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version From 6969ebdcb76b406db8e65bd8e051d99b1ab057cc Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 21:16:38 +0330 Subject: [PATCH 42/82] Update .travis.yml --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 529df5912..2bd4b2743 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,8 +37,7 @@ install: - tar xfz mongodb-linux-x86_64-$MONGODB_VERSION.tgz - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - - mongod --dbpath=data/db --replSet "rs0" & - - sleep 30 + - travis_wait 0.5 mongod --dbpath=data/db --replSet "rs0" - mongo --eval 'rs.initiate()' - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version From 09d24dbd694a768f5f648a3f0a6286429259ca5f Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 21:20:37 +0330 Subject: [PATCH 43/82] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2bd4b2743..243ebe2c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ install: - tar xfz mongodb-linux-x86_64-$MONGODB_VERSION.tgz - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - - travis_wait 0.5 mongod --dbpath=data/db --replSet "rs0" + - travis_wait 1 mongod --dbpath=data/db --replSet "rs0" - mongo --eval 'rs.initiate()' - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version From 084aae8b08ebe1676db01f8614f53713d810472b Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 21:25:46 +0330 Subject: [PATCH 44/82] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 243ebe2c7..aac6b0bfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ install: - tar xfz mongodb-linux-x86_64-$MONGODB_VERSION.tgz - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - - travis_wait 1 mongod --dbpath=data/db --replSet "rs0" + - travis_wait 2 mongod --dbpath=data/db --replSet "rs0" - mongo --eval 'rs.initiate()' - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version From 5cf4da0a207d51b3a61a9059563a8d3037f9e496 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 21:36:35 +0330 Subject: [PATCH 45/82] Update .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index aac6b0bfb..529df5912 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,8 @@ install: - tar xfz mongodb-linux-x86_64-$MONGODB_VERSION.tgz - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - - travis_wait 2 mongod --dbpath=data/db --replSet "rs0" + - mongod --dbpath=data/db --replSet "rs0" & + - sleep 30 - mongo --eval 'rs.initiate()' - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version From df1e43cd66235f9ae13366b751ace73ba462a9fe Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 21:51:02 +0330 Subject: [PATCH 46/82] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 529df5912..3162317ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ install: - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - mongod --dbpath=data/db --replSet "rs0" & - - sleep 30 + - sleep 70 - mongo --eval 'rs.initiate()' - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version From a6e9954fade1da8aee64ca464e52f3f68eeae50f Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 22:00:12 +0330 Subject: [PATCH 47/82] Update .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3162317ec..0261c464c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,8 +38,9 @@ install: - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - mongod --dbpath=data/db --replSet "rs0" & - - sleep 70 + - sleep 5 - mongo --eval 'rs.initiate()' + - mongo --eval ';' - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version - export PATH="$HOME/.composer/vendor/bin:$PATH" From d78531a3baf58b366c90fd0b0a7a4769b7fd7863 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 22:06:48 +0330 Subject: [PATCH 48/82] update --- .travis.yml | 1 - tests/data/travis/mongodb-setup.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0261c464c..b167b915d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,6 @@ install: - mongod --dbpath=data/db --replSet "rs0" & - sleep 5 - mongo --eval 'rs.initiate()' - - mongo --eval ';' - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version - export PATH="$HOME/.composer/vendor/bin:$PATH" diff --git a/tests/data/travis/mongodb-setup.sh b/tests/data/travis/mongodb-setup.sh index c709dd832..50871265a 100755 --- a/tests/data/travis/mongodb-setup.sh +++ b/tests/data/travis/mongodb-setup.sh @@ -7,7 +7,7 @@ echo "MongoDB Server version:" mongod --version -mongo yii2test --eval 'db.createUser({user: "travis", pwd: "test", roles: ["readWrite", "dbAdmin"]});' +mongo mongo "mongodb://127.0.0.1/yii2test?replicaSet=rs0" --eval 'db.createUser({user: "travis", pwd: "test", roles: ["readWrite", "dbAdmin"]});' # PHP Extension : From bb77751ca4f5943f06e6ae93a5695963eb5e6c85 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 22:13:04 +0330 Subject: [PATCH 49/82] Update mongodb-setup.sh --- tests/data/travis/mongodb-setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data/travis/mongodb-setup.sh b/tests/data/travis/mongodb-setup.sh index 50871265a..c54454715 100755 --- a/tests/data/travis/mongodb-setup.sh +++ b/tests/data/travis/mongodb-setup.sh @@ -7,7 +7,7 @@ echo "MongoDB Server version:" mongod --version -mongo mongo "mongodb://127.0.0.1/yii2test?replicaSet=rs0" --eval 'db.createUser({user: "travis", pwd: "test", roles: ["readWrite", "dbAdmin"]});' +mongo yii2test --noauth --eval 'db.createUser({user: "travis", pwd: "test", roles: ["readWrite", "dbAdmin"]});' # PHP Extension : From c62c57795bd16a37ed4535bdfa6105a96bc018d4 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 22:32:05 +0330 Subject: [PATCH 50/82] update --- .travis.yml | 2 +- tests/data/travis/mongodb-setup.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b167b915d..65c93fb2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ install: - tar xfz mongodb-linux-x86_64-$MONGODB_VERSION.tgz - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - - mongod --dbpath=data/db --replSet "rs0" & + - mongod --noauth --dbpath=data/db --replSet "rs0" & - sleep 5 - mongo --eval 'rs.initiate()' - tests/data/travis/mongodb-setup.sh diff --git a/tests/data/travis/mongodb-setup.sh b/tests/data/travis/mongodb-setup.sh index c54454715..c709dd832 100755 --- a/tests/data/travis/mongodb-setup.sh +++ b/tests/data/travis/mongodb-setup.sh @@ -7,7 +7,7 @@ echo "MongoDB Server version:" mongod --version -mongo yii2test --noauth --eval 'db.createUser({user: "travis", pwd: "test", roles: ["readWrite", "dbAdmin"]});' +mongo yii2test --eval 'db.createUser({user: "travis", pwd: "test", roles: ["readWrite", "dbAdmin"]});' # PHP Extension : From 9a626854da1228aaf1e96e03dd403125b641f838 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 22:38:50 +0330 Subject: [PATCH 51/82] Update .travis.yml --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 65c93fb2f..4e10b16d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,10 +37,13 @@ install: - tar xfz mongodb-linux-x86_64-$MONGODB_VERSION.tgz - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db + - mongod --dbpath=data/db & + - sleep 5 + - tests/data/travis/mongodb-setup.sh + - pkill -f "mongod" - mongod --noauth --dbpath=data/db --replSet "rs0" & - sleep 5 - mongo --eval 'rs.initiate()' - - tests/data/travis/mongodb-setup.sh - travis_retry composer self-update && composer --version - export PATH="$HOME/.composer/vendor/bin:$PATH" - travis_retry composer install --prefer-dist --no-interaction From e9231bb56a29efb3981b1caa205bf6c2e0b1171d Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 23:02:50 +0330 Subject: [PATCH 52/82] Update .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4e10b16d9..a63bc6633 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,7 @@ install: - sleep 5 - tests/data/travis/mongodb-setup.sh - pkill -f "mongod" + - sleep 5 - mongod --noauth --dbpath=data/db --replSet "rs0" & - sleep 5 - mongo --eval 'rs.initiate()' From 6e0e6ef7b3ff5ff4a0bd12b41ecc0fbf5b87f722 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 23:19:02 +0330 Subject: [PATCH 53/82] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a63bc6633..5882c6a7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,12 +37,12 @@ install: - tar xfz mongodb-linux-x86_64-$MONGODB_VERSION.tgz - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - - mongod --dbpath=data/db & + - mongod --quiet --dbpath=data/db & - sleep 5 - tests/data/travis/mongodb-setup.sh - pkill -f "mongod" - sleep 5 - - mongod --noauth --dbpath=data/db --replSet "rs0" & + - mongod --quiet --noauth --dbpath=data/db --replSet "rs0" & - sleep 5 - mongo --eval 'rs.initiate()' - travis_retry composer self-update && composer --version From c3c965396e7b4fee36751a1ade02419751854d4c Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 11 Oct 2020 23:19:46 +0330 Subject: [PATCH 54/82] Update .travis.yml --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5882c6a7d..7e265146d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,9 +17,6 @@ matrix: - php: "5.5" dist: trusty - - php: "5.4" - dist: trusty - # faster builds on new travis setup not using sudo sudo: false From 4ee0ca922dbce906f437aadef42bc32c536efa9c Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Mon, 12 Oct 2020 07:51:51 +0330 Subject: [PATCH 55/82] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7e265146d..657278336 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,12 +34,12 @@ install: - tar xfz mongodb-linux-x86_64-$MONGODB_VERSION.tgz - export PATH=`pwd`/mongodb-linux-x86_64-$MONGODB_VERSION/bin:$PATH - mkdir -p data/db - - mongod --quiet --dbpath=data/db & + - mongod --quiet --dbpath=data/db > /dev/null & - sleep 5 - tests/data/travis/mongodb-setup.sh - pkill -f "mongod" - sleep 5 - - mongod --quiet --noauth --dbpath=data/db --replSet "rs0" & + - mongod --quiet --noauth --dbpath=data/db --replSet "rs0" > /dev/null & - sleep 5 - mongo --eval 'rs.initiate()' - travis_retry composer self-update && composer --version From c1dff46fd96613aa461015b2b15f9b73bf024c78 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Wed, 28 Oct 2020 19:01:08 +0330 Subject: [PATCH 56/82] adds `Once` option and modifies documents --- src/ActiveRecord.php | 37 +++++++++-------- src/ClientSession.php | 20 +++++----- src/Command.php | 5 ++- src/Connection.php | 92 +++++++++++++++++++++++++++++-------------- 4 files changed, 95 insertions(+), 59 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 19eeb7ebd..6066424e3 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -520,13 +520,13 @@ private function dumpBsonObject(Type $object) } /** - * Lock a document in a transaction(like `select for update` feature in mysql) + * Locks a document of the collection in a transaction(like `select for update` feature in mysql) * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions * @param mixed $id a document id(primary key > _id) - * @param array $options list of options in format: optionName => optionValue. - * @param Connection $db the Mongo connection used to execute the query. - * @return ActiveRecord|null the modified document. - * Returns instance of ActiveRecord. Null will be returned if the query results in nothing. + * @param array $options list of the options in format: optionName => optionValue. + * @param Connection $db the Mongo connection uses it to execute the query. + * @return ActiveRecord|null the locked document. + * Returns instance of ActiveRecord. Null will be returned if the query does not have a result. */ public static function LockDocument($id, $options = [], $db = null){ $db = $db ? $db : static::getDb(); @@ -542,23 +542,22 @@ public static function LockDocument($id, $options = [], $db = null){ /** * locking a document in stubborn mode on a transaction(like `select for update` feature in mysql) * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions - * notice : before call this method you must save last mongodb client session from db connection - * notice : this lock occurred in a new session and transaction + * notice : you can not use stubborn mode if transaction is started in current session(or use your session with `mySession` parameter). * @param mixed $id a document id(primary key > _id) * @param array $options list of options in format: * [ - * 'mySession' => false, #custom session instance of ClientSession. + * 'mySession' => false, #a custom session instance of ClientSession for start a transaction. * 'transactionOptions' => [], #new transaction options. see $transactionOptions in Transaction::start() * 'modifyOptions' => [], #see $options in ActiveQuery::modify() - * 'sleep' => 1000000, #time in microseconds for wait. default is one second. - * 'tiredAfter' => 0, #maximum count of retry. throw write conflict error after reached this value. zero default is unlimited. + * 'sleep' => 1000000, #a time parameter in microseconds to wait. the default is one second. + * 'try' => 0, #maximum count of retry. throw write conflict error after reached this value. the zero default is unlimited. * ] - * @param Connection $db the Mongo connection used to execute the query. - * @return ActiveRecord|null the modified document. - * Returns instance of ActiveRecord. Null will be returned if the query results in nothing. - * Throw write conflict error after reached $options['tiredAfter'] value + * @param Connection $db the Mongo connection uses it to execute the query. + * @return ActiveRecord|null returns the locked document. + * Returns instance of ActiveRecord. Null will be returned if the query does not have a result. + * When the total number of attempts to lock the document passes `try`, conflict error will be thrown */ - public static function StubbornLockDocument($id, $options = [], $db = null){ + public static function LockDocumentStubbornly($id, $options = [], $db = null){ $db = $db ? $db : static::getDb(); @@ -566,13 +565,13 @@ public static function StubbornLockDocument($id, $options = [], $db = null){ 'mySession' => false, 'transactionOptions' => [], 'modifyOptions' => [], - 'sleep' => 1000000, #in microseconds - 'tiredAfter' => 0, + 'sleep' => 1000000, + 'try' => 0, ],$options); $options['modifyOptions']['new'] = true; - $session = $options['mySession'] ? $options['mySession'] : $db->startSession([],true); + $session = $options['mySession'] ? $options['mySession'] : $db->startSessionOnce(); if($session->getInTransaction()) throw new Exception('You can\'t use stubborn lock feature because current connection is in a transaction.'); @@ -591,7 +590,7 @@ public static function StubbornLockDocument($id, $options = [], $db = null){ }catch(\Exception $e){ $session->transaction->rollBack(); $tiredCounter++; - if($options['tiredAfter'] !== 0 && $tiredCounter === $options['tiredAfter']) + if($options['try'] !== 0 && $tiredCounter === $options['try']) throw $e; usleep($options['sleep']); goto StartStubborn; diff --git a/src/ClientSession.php b/src/ClientSession.php index b81ff8eb2..294539ecb 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -15,8 +15,8 @@ /** * ClientSession represents a client session and Commands, queries, and write operations may then be associated the session. * @see https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions - * Note : At least 1.4.0 mongodb php driver version is supported. - * Note : At least 3.6 MongoDB version is supported. + * Note : The minimum supported version of mongodb php driver is 1.4.0 + * Note : The minimum supported version of MongoDB server is 3.6 * @see https://github.com/mongodb/mongo-php-driver/releases/tag/1.4.0 * @see https://docs.mongodb.com/ecosystem/drivers/php/#mongodb-compatibility * @author Abolfazl Ziaratban @@ -37,12 +37,12 @@ class ClientSession extends \yii\base\BaseObject public $mongoSession; /** - * @var Transaction current transaction in session. this transaction can only be created once. + * @var Transaction The current transaction in session. this transaction can only be created once. */ private $_transaction = null; /** - * preapare options for some purpose + * Prepares options for some purposes * @param array by reference * convert string option to object * [ @@ -102,11 +102,11 @@ public function getId(){ } /** - * Start a new session in a connection. + * Starts a new mongodb session in a connection. * @param Connection $db * @param Array $sessionOptions Creates a ClientSession for the given options * @see https://www.php.net/manual/en/mongodb-driver-manager.startsession.php#refsect1-mongodb-driver-manager.startsession-parameters - * @return ClientSession return new session base on a session options for the given connection + * @return ClientSession returns new session base on a session options for the given connection */ public static function start($db, $sessionOptions = []){ self::prepareOptions($sessionOptions); @@ -123,8 +123,8 @@ public static function start($db, $sessionOptions = []){ } /** - * Get current transaction of session or create a new transaction once - * @return Transaction return current transaction + * Gets a current transaction of session or creates a new transaction once + * @return Transaction returns current transaction */ public function getTransaction(){ if($this->_transaction === null) @@ -133,7 +133,7 @@ public function getTransaction(){ } /** - * Returns whether a multi-document transaction is in progress + * Returns true if the transaction is in progress * @return bool */ public function getInTransaction(){ @@ -141,7 +141,7 @@ public function getInTransaction(){ } /** - * End current session + * Ends the current session. */ public function end(){ $this->mongoSession->endSession(); diff --git a/src/Command.php b/src/Command.php index b34e0c255..4ca90698b 100644 --- a/src/Command.php +++ b/src/Command.php @@ -73,10 +73,13 @@ class Command extends BaseObject */ public $document = []; + /** + * @var array default options for `executeCommand` method of MongoDB\Driver\Manager. + */ public $globalExecOptions = []; /** - * prepare execOptions for some purpose + * prepares execOptions for some purposes * @param array|object by reference see Connection::prepareExceOptions */ private function prepareExecOptions(&$execOptions){ diff --git a/src/Connection.php b/src/Connection.php index bd45ee094..fc5f0903b 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -175,6 +175,9 @@ class Connection extends Component */ public $fileStreamWrapperClass = 'yii\mongodb\file\StreamWrapper'; + /** + * @var array default options for `executeCommand` method of MongoDB\Driver\Manager in `Command` class. + */ public $globalExecOptions = []; /** @@ -457,8 +460,7 @@ public function registerFileStreamWrapper($force = false) } /** - * set global execOptions for Command::execute() and Command::executeBatch() and Command::query() - * this options when set if internal $execOptions is not set. + * Sets global execOptions for Command::execute() and Command::executeBatch() and Command::query() * @param array $execOptions see docs of Command::execute() and Command::executeBatch() and Command::query() * @return $this */ @@ -471,26 +473,33 @@ public function execOptions($execOptions){ } /** - * Ends the previous session and starts new session for current connection. + * Ends the previous session and starts the new session. * @param array $sessionOptions see doc of ClientSession::start() - * @param array $skip when set to true , no creates new session if exists. * return ClientSession */ - public function startSession($sessionOptions = [], $skip = false){ + public function startSession($sessionOptions = []){ - if($this->getInSession()){ - if($skip) - return $this->getSession(); + if($this->getInSession()) $this->getSession()->end(); - } - $newSession = ClientSession::start($this, $sessionOptions); + $newSession = $this->newSession($sessionOptions); $this->setSession($newSession); return $newSession; } /** - * only starts new session for current connection and that session does not sets for current connection. + * Starts a new session if the session has not started, otherwise returns previous session. + * @param array $sessionOptions see doc of ClientSession::start() + * return ClientSession + */ + public function startSessionOnce($sessionOptions = []){ + if($this->getInSession()) + return $this->getSession(); + return $this->startSession($sessionOptions); + } + + /** + * Only starts the new session for current connection but this session does not set for current connection. * @param array $sessionOptions see doc of ClientSession::start() * return ClientSession */ @@ -499,7 +508,7 @@ public function newSession($sessionOptions = []){ } /** - * check if current connection is in session + * Checks whether the current connection is in session. * return bool */ public function getInSession(){ @@ -507,7 +516,7 @@ public function getInSession(){ } /** - * check if current connection is in session and transaction + * Checks that the current connection is in session and transaction * return bool */ public function getInTransaction(){ @@ -515,7 +524,7 @@ public function getInTransaction(){ } /** - * throw custome error if transaction is not ready in connection + * Throws custom error if transaction is not ready in connection * @param string $operation a custom message to be shown */ public function transactionReady($operation){ @@ -526,7 +535,7 @@ public function transactionReady($operation){ } /** - * return current session + * Returns current session * return ClientSession|null */ public function getSession(){ @@ -534,10 +543,10 @@ public function getSession(){ } /** - * start transaction with three step : + * Starts a transaction with three steps : * - starts new session if has not started - * - start transaction of new session - * - set new session to current connection + * - starts the transaction in new session + * - sets new session to current connection * @param array $transactionOptions see doc of Transaction::start() * @param array $sessionOptions see doc of ClientSession::start() * return ClientSession @@ -549,7 +558,19 @@ public function startTransaction($transactionOptions = [], $sessionOptions = []) } /** - * commit transaction in current session + * Starts a transaction in current session if the previous transaction was not started in current session. + * @param array $transactionOptions see doc of Transaction::start() + * @param array $sessionOptions see doc of ClientSession::start() + * return ClientSession + */ + public function startTransactionOnce($transactionOptions = [], $sessionOptions = []){ + if($this->getInTransaction()) + return $this->getSession(); + return $this->startTransaction($transactionOptions,$sessionOptions); + } + + /** + * Commits transaction in current session */ public function commitTransaction(){ $this->transactionReady('commit transaction'); @@ -557,7 +578,7 @@ public function commitTransaction(){ } /** - * rollback transaction in current session + * Rollbacks transaction in current session */ public function rollBackTransaction(){ $this->transactionReady('roll back transaction'); @@ -565,8 +586,8 @@ public function rollBackTransaction(){ } /** - * change current session of command (or drop session) - * @param ClientSession|null $clientSession new instance of ClientSession for replace + * Changes the current session of connection to execute commands (or drop session) + * @param ClientSession|null $clientSession new instance of ClientSession to replace * return $this */ public function setSession($clientSession){ @@ -579,9 +600,9 @@ public function setSession($clientSession){ } /** - * easy start and commit transaction - * @param callable $actions your block of code must be run after transaction started and before commit - * if $actions return false then transaction rolled back. + * Starts and commits a transaction in easy mode. + * @param callable $actions your block of code must be runned after transaction started and before commit + * if the $actions returns false then transaction rolls back. * @param array $transactionOptions see doc of Transaction::start() * @param array $sessionOptions see doc of ClientSession::start() */ @@ -603,10 +624,23 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio } /** - * run your mongodb command out of session and transaction. - * returns last mongodb session to current session after end of codes. - * @param callable $actions your block of code must be run out of session and transaction - * return result of $actions() + * Starts and commits transaction in easy mode if the previous transaction was not executed, + * otherwise only runs your actions in previous transaction. + * @param callable $actions your block of code must be runned after transaction started and before commit + * @param array $transactionOptions see doc of Transaction::start() + * @param array $sessionOptions see doc of ClientSession::start() + */ + public function transactionOnce(callable $actions, $transactionOptions = [], $sessionOptions = []){ + if($this->getInTransaction()) + $action(); + else + $this->transaction($action,$transactionOptions,$sessionOptions); + } + + /** + * Runs your mongodb command out of session and transaction. + * @param callable $actions your block of code must be runned out of session and transaction + * @return mixed returns a result of $actions() */ public function noTransaction(callable $actions){ $lastSession = $this->getSession(); From b604a33b36efc29e393ab1726e3ebecdcfa6424b Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 29 Oct 2020 08:21:26 +0330 Subject: [PATCH 57/82] Update Connection.php --- src/Connection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Connection.php b/src/Connection.php index fc5f0903b..787c33dc7 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -632,9 +632,9 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio */ public function transactionOnce(callable $actions, $transactionOptions = [], $sessionOptions = []){ if($this->getInTransaction()) - $action(); + $actions(); else - $this->transaction($action,$transactionOptions,$sessionOptions); + $this->transaction($actions,$transactionOptions,$sessionOptions); } /** From 82e110233d36352bffa83e78f6b17b06fd2bd4db Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 30 Oct 2020 21:07:05 +0300 Subject: [PATCH 58/82] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc13d95d4..5870d0898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Yii Framework 2 mongodb extension Change Log 2.1.10 under development ------------------------ -- Enh #294: transactions support (ziaratban) +- Enh #294: Add transactions support (ziaratban) - Bug #308: Fix `yii\mongodb\file\Upload::addFile()` error when uploading file with readonly permissions (sparchatus) From 9b4173f003c30bafe19f3628886395c54e6b0fe0 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 24 Apr 2022 09:45:19 +0430 Subject: [PATCH 59/82] remove static lockField Set the lock field name in the method. --- src/ActiveRecord.php | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 6066424e3..f9bcadaf3 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -47,12 +47,6 @@ abstract class ActiveRecord extends BaseActiveRecord */ const OP_ALL = 0x07; - /** - * @var string default lock field name in LockDocument() method - * this property can be define by end user - */ - public static $lockField = '_lock'; - /** * Returns the Mongo connection used by this AR class. * By default, the "mongodb" application component is used as the Mongo connection. @@ -523,19 +517,20 @@ private function dumpBsonObject(Type $object) * Locks a document of the collection in a transaction(like `select for update` feature in mysql) * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions * @param mixed $id a document id(primary key > _id) - * @param array $options list of the options in format: optionName => optionValue. + * @param string $lockFieldName The name of the field you want to lock. default is '_lock' + * @param array $modifyOptions list of the options in format: optionName => optionValue. * @param Connection $db the Mongo connection uses it to execute the query. * @return ActiveRecord|null the locked document. * Returns instance of ActiveRecord. Null will be returned if the query does not have a result. */ - public static function LockDocument($id, $options = [], $db = null){ + public static function LockDocument($id, $lockFieldName = '_lock', $modifyOptions = [], $db = null){ $db = $db ? $db : static::getDb(); $db->transactionReady('lock document'); $options['new'] = true; return static::find() ->where(['_id' => $id]) - ->modify(['$set' => [static::$lockField => new ObjectId]], $options, $db) + ->modify(['$set' => [$lockFieldName => new ObjectId]], $modifyOptions, $db) ; } @@ -546,11 +541,12 @@ public static function LockDocument($id, $options = [], $db = null){ * @param mixed $id a document id(primary key > _id) * @param array $options list of options in format: * [ - * 'mySession' => false, #a custom session instance of ClientSession for start a transaction. - * 'transactionOptions' => [], #new transaction options. see $transactionOptions in Transaction::start() - * 'modifyOptions' => [], #see $options in ActiveQuery::modify() - * 'sleep' => 1000000, #a time parameter in microseconds to wait. the default is one second. - * 'try' => 0, #maximum count of retry. throw write conflict error after reached this value. the zero default is unlimited. + * 'mySession' => false, #A custom session instance of ClientSession for start a transaction. + * 'transactionOptions' => [], #New transaction options. see $transactionOptions in Transaction::start() + * 'modifyOptions' => [], #See $options in ActiveQuery::modify() + * 'sleep' => 1000000, #A time parameter in microseconds to wait. the default is one second. + * 'try' => 0, #Maximum count of retry. throw write conflict error after reached this value. the zero default is unlimited. + * 'lockFieldName' => #The name of the field you want to lock. default is '_lock' * ] * @param Connection $db the Mongo connection uses it to execute the query. * @return ActiveRecord|null returns the locked document. @@ -567,6 +563,7 @@ public static function LockDocumentStubbornly($id, $options = [], $db = null){ 'modifyOptions' => [], 'sleep' => 1000000, 'try' => 0, + 'lockFieldName' => '_lock', ],$options); $options['modifyOptions']['new'] = true; @@ -584,7 +581,7 @@ public static function LockDocumentStubbornly($id, $options = [], $db = null){ $doc = static::find() ->where(['_id' => $id]) - ->modify(['$set' => [static::$lockField => new ObjectId]], $options['modifyOptions'], $db) + ->modify(['$set' => [$options['lockFieldName'] => new ObjectId]], $options['modifyOptions'], $db) ; return $doc; }catch(\Exception $e){ From f1e5b2b5bf2be76c85e977b30024e68b099f2070 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Sun, 24 Apr 2022 09:52:19 +0430 Subject: [PATCH 60/82] Update ActiveRecord.php --- src/ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index f9bcadaf3..7f4e9c950 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -546,7 +546,7 @@ public static function LockDocument($id, $lockFieldName = '_lock', $modifyOption * 'modifyOptions' => [], #See $options in ActiveQuery::modify() * 'sleep' => 1000000, #A time parameter in microseconds to wait. the default is one second. * 'try' => 0, #Maximum count of retry. throw write conflict error after reached this value. the zero default is unlimited. - * 'lockFieldName' => #The name of the field you want to lock. default is '_lock' + * 'lockFieldName' => '_lock' #The name of the field you want to lock. default is '_lock' * ] * @param Connection $db the Mongo connection uses it to execute the query. * @return ActiveRecord|null returns the locked document. From 0ab79730cad25925102847cf18922b0842497342 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:24:00 +0430 Subject: [PATCH 61/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 7f4e9c950..ce6e21dfa 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -231,11 +231,12 @@ public function insert($runValidation = true, $attributes = null) return false; } - if(!$this->isTransactional(self::OP_INSERT)) + if (!$this->isTransactional(self::OP_INSERT)) { return $this->insertInternal($attributes); + } $result = null; - static::getDb()->transaction(function()use($attribute,&$result){ + static::getDb()->transaction(function() use ($attribute, &$result) { $result = $this->insertInternal($attributes); }); return $result; From e95f147994a020889677a7a4528dbb44ce65c40f Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:24:25 +0430 Subject: [PATCH 62/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index ce6e21dfa..5964724a0 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -331,11 +331,12 @@ public function update($runValidation = true, $attributeNames = null) return false; } - if(!$this->isTransactional(self::OP_UPDATE)) + if (!$this->isTransactional(self::OP_UPDATE)) { return $this->updateInternal($attributeNames); + } $result = null; - static::getDb()->transaction(function()use($attributeNames,&$result){ + static::getDb()->transaction(function() use ($attributeNames, &$result) { $result = $this->updateInternal($attributeNames); }); return $result; From a20c1629fd1dad1bbec2b0e304641e3272288d4d Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:24:34 +0430 Subject: [PATCH 63/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 5964724a0..98648640e 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -407,8 +407,9 @@ protected function updateInternal($attributes = null) */ public function delete() { - if(!$this->isTransactional(self::OP_DELETE)) + if(!$this->isTransactional(self::OP_DELETE)) { return $this->deleteInternal(); + } $result = null; static::getDb()->transaction(function()use(&$result){ From ccbc6c9e0a98718661bc688cf8bd6a99c60585c2 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:24:42 +0430 Subject: [PATCH 64/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 98648640e..7d1af4258 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -412,7 +412,7 @@ public function delete() } $result = null; - static::getDb()->transaction(function()use(&$result){ + static::getDb()->transaction(function() use (&$result) { $result = $this->deleteInternal(); }); return $result; From ca3548606c3a776f7481bcef715ae40d77c89e2c Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:24:48 +0430 Subject: [PATCH 65/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 7d1af4258..df926b610 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -424,8 +424,9 @@ public function delete() */ protected function deleteInternal() { - if(!$this->beforeDelete()) + if (!$this->beforeDelete()) { return false; + } // we do not check the return value of deleteAll() because it's possible // the record is already deleted in the database and thus the method will return 0 $condition = $this->getOldPrimaryKey(true); From 380983b7906257cd439abb566d256fd2ad4d5549 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:25:03 +0430 Subject: [PATCH 66/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index df926b610..14935c2a6 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -527,7 +527,8 @@ private function dumpBsonObject(Type $object) * @return ActiveRecord|null the locked document. * Returns instance of ActiveRecord. Null will be returned if the query does not have a result. */ - public static function LockDocument($id, $lockFieldName = '_lock', $modifyOptions = [], $db = null){ + public static function LockDocument($id, $lockFieldName = '_lock', $modifyOptions = [], $db = null) + { $db = $db ? $db : static::getDb(); $db->transactionReady('lock document'); $options['new'] = true; From 401eb55f767099eadcd0b993b1958aecb0372a71 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:25:10 +0430 Subject: [PATCH 67/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 14935c2a6..22d02f979 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -558,8 +558,8 @@ public static function LockDocument($id, $lockFieldName = '_lock', $modifyOption * Returns instance of ActiveRecord. Null will be returned if the query does not have a result. * When the total number of attempts to lock the document passes `try`, conflict error will be thrown */ - public static function LockDocumentStubbornly($id, $options = [], $db = null){ - + public static function LockDocumentStubbornly($id, $options = [], $db = null) + { $db = $db ? $db : static::getDb(); $options = array_replace_recursive([ From e75c4a14bba124f722f13b4567500157c5e15197 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:25:37 +0430 Subject: [PATCH 68/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 22d02f979..311ca24ce 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -540,7 +540,7 @@ public static function LockDocument($id, $lockFieldName = '_lock', $modifyOption } /** - * locking a document in stubborn mode on a transaction(like `select for update` feature in mysql) + * Locking a document in stubborn mode on a transaction (like `select for update` feature in MySQL) * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions * notice : you can not use stubborn mode if transaction is started in current session(or use your session with `mySession` parameter). * @param mixed $id a document id(primary key > _id) From 39e32861363cdc062e2851b5d0457fe3bbbdae9b Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:25:42 +0430 Subject: [PATCH 69/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 311ca24ce..4eedaff64 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -575,8 +575,9 @@ public static function LockDocumentStubbornly($id, $options = [], $db = null) $session = $options['mySession'] ? $options['mySession'] : $db->startSessionOnce(); - if($session->getInTransaction()) + if ($session->getInTransaction()) { throw new Exception('You can\'t use stubborn lock feature because current connection is in a transaction.'); + } #start stubborn $tiredCounter = 0; From 9e8f9f51cd7173d28580c91ec8e9ca9bc5502569 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:25:51 +0430 Subject: [PATCH 70/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 4eedaff64..8f567458f 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -569,7 +569,7 @@ public static function LockDocumentStubbornly($id, $options = [], $db = null) 'sleep' => 1000000, 'try' => 0, 'lockFieldName' => '_lock', - ],$options); + ], $options); $options['modifyOptions']['new'] = true; From 8c397919b63d3cf8f5abfedb7b6ff6ea8a7124af Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Thu, 28 Apr 2022 11:26:00 +0430 Subject: [PATCH 71/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 8f567458f..cc65ac9da 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -579,22 +579,29 @@ public static function LockDocumentStubbornly($id, $options = [], $db = null) throw new Exception('You can\'t use stubborn lock feature because current connection is in a transaction.'); } - #start stubborn + // start stubborn $tiredCounter = 0; StartStubborn: $session->transaction->start($options['transactionOptions']); - try{ - $doc = - static::find() - ->where(['_id' => $id]) - ->modify(['$set' => [$options['lockFieldName'] => new ObjectId]], $options['modifyOptions'], $db) - ; + try { + $doc = static::find() + ->where(['_id' => $id]) + ->modify( + [ + '$set' => [ + $options['lockFieldName'] => new ObjectId + ] + ], + $options['modifyOptions'], + $db + ); return $doc; - }catch(\Exception $e){ + } catch(\Exception $e) { $session->transaction->rollBack(); $tiredCounter++; - if($options['try'] !== 0 && $tiredCounter === $options['try']) + if ($options['try'] !== 0 && $tiredCounter === $options['try']) { throw $e; + } usleep($options['sleep']); goto StartStubborn; } From f42ff05e7b911b6551673a35ee8bf25e3c58794e Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Thu, 28 Apr 2022 12:48:50 +0430 Subject: [PATCH 72/82] Clean code & a one development add $lockFieldName param to LockDocumentStubbornly and lockFieldName is required in both lock methods --- src/ActiveRecord.php | 39 ++++++++++------- src/ClientSession.php | 78 ++++++++++++++++++++++------------ src/Command.php | 43 ++++++++++--------- src/Connection.php | 97 ++++++++++++++++++++++++++++--------------- src/Transaction.php | 42 ++++++++++++------- 5 files changed, 186 insertions(+), 113 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index cc65ac9da..cb2ab9b5f 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -521,21 +521,26 @@ private function dumpBsonObject(Type $object) * Locks a document of the collection in a transaction(like `select for update` feature in mysql) * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions * @param mixed $id a document id(primary key > _id) - * @param string $lockFieldName The name of the field you want to lock. default is '_lock' + * @param string $lockFieldName The name of the field you want to lock. * @param array $modifyOptions list of the options in format: optionName => optionValue. * @param Connection $db the Mongo connection uses it to execute the query. * @return ActiveRecord|null the locked document. * Returns instance of ActiveRecord. Null will be returned if the query does not have a result. */ - public static function LockDocument($id, $lockFieldName = '_lock', $modifyOptions = [], $db = null) + public static function LockDocument($id, $lockFieldName, $modifyOptions = [], $db = null) { $db = $db ? $db : static::getDb(); $db->transactionReady('lock document'); $options['new'] = true; - return - static::find() - ->where(['_id' => $id]) - ->modify(['$set' => [$lockFieldName => new ObjectId]], $modifyOptions, $db) + return static::find() + ->where(['_id' => $id]) + ->modify( + [ + '$set' =>[$lockFieldName => new ObjectId] + ], + $modifyOptions, + $db + ) ; } @@ -558,18 +563,20 @@ public static function LockDocument($id, $lockFieldName = '_lock', $modifyOption * Returns instance of ActiveRecord. Null will be returned if the query does not have a result. * When the total number of attempts to lock the document passes `try`, conflict error will be thrown */ - public static function LockDocumentStubbornly($id, $options = [], $db = null) + public static function LockDocumentStubbornly($id, $lockFieldName, $options = [], $db = null) { $db = $db ? $db : static::getDb(); - $options = array_replace_recursive([ - 'mySession' => false, - 'transactionOptions' => [], - 'modifyOptions' => [], - 'sleep' => 1000000, - 'try' => 0, - 'lockFieldName' => '_lock', - ], $options); + $options = array_replace_recursive( + [ + 'mySession' => false, + 'transactionOptions' => [], + 'modifyOptions' => [], + 'sleep' => 1000000, + 'try' => 0, + ], + $options + ); $options['modifyOptions']['new'] = true; @@ -589,7 +596,7 @@ public static function LockDocumentStubbornly($id, $options = [], $db = null) ->modify( [ '$set' => [ - $options['lockFieldName'] => new ObjectId + $lockFieldName => new ObjectId ] ], $options['modifyOptions'], diff --git a/src/ClientSession.php b/src/ClientSession.php index 294539ecb..9ad7db123 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -37,7 +37,7 @@ class ClientSession extends \yii\base\BaseObject public $mongoSession; /** - * @var Transaction The current transaction in session. this transaction can only be created once. + * @var Transaction the current transaction in session. this transaction can only be created once. */ private $_transaction = null; @@ -63,31 +63,47 @@ class ClientSession extends \yii\base\BaseObject * ], * ] */ - public static function prepareOptions(&$options){ + public static function prepareOptions(&$options) + { + if (array_key_exists('defaultTransactionOptions', $options)) { - if(array_key_exists('defaultTransactionOptions',$options)){ - - #convert readConcern - if( - array_key_exists('readConcern',$options['defaultTransactionOptions']) && + //convert readConcern + if ( + array_key_exists('readConcern', $options['defaultTransactionOptions']) && is_string($options['defaultTransactionOptions']['readConcern']) - ) + ) { $options['defaultTransactionOptions']['readConcern'] = new ReadConcern($options['defaultTransactionOptions']['readConcern']); + } - #convert writeConcern - if(array_key_exists('writeConcern',$options['defaultTransactionOptions'])){ - if(is_string($options['defaultTransactionOptions']['writeConcern']) || is_int($options['defaultTransactionOptions']['writeConcern'])) + //convert writeConcern + if (array_key_exists('writeConcern',$options['defaultTransactionOptions'])) { + if ( + is_string($options['defaultTransactionOptions']['writeConcern']) || + is_int($options['defaultTransactionOptions']['writeConcern']) + ) { $options['defaultTransactionOptions']['writeConcern'] = new WriteConcern($options['defaultTransactionOptions']['writeConcern']); - else if(is_array($options['defaultTransactionOptions']['writeConcern'])) - $options['defaultTransactionOptions']['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($options['defaultTransactionOptions']['writeConcern']); + } elseif (is_array($options['defaultTransactionOptions']['writeConcern'])) { + $options['defaultTransactionOptions']['writeConcern'] = + (new \ReflectionClass('\MongoDB\Driver\WriteConcern')) + ->newInstanceArgs( + $options['defaultTransactionOptions']['writeConcern'] + ) + ; + } } - #convert readPreference - if(array_key_exists('readPreference',$options['defaultTransactionOptions'])){ - if(is_string($options['defaultTransactionOptions']['readPreference'])) + //Convert readPreference + if (array_key_exists('readPreference',$options['defaultTransactionOptions'])) { + if (is_string($options['defaultTransactionOptions']['readPreference'])) { $options['defaultTransactionOptions']['readPreference'] = new ReadPreference($options['defaultTransactionOptions']['readPreference']); - else if(is_array($options['defaultTransactionOptions']['readPreference'])) - $options['defaultTransactionOptions']['readPreference'] = (new \ReflectionClass('\MongoDB\Driver\ReadPreference'))->newInstanceArgs($options['defaultTransactionOptions']['readPreference']); + } else if(is_array($options['defaultTransactionOptions']['readPreference'])) { + $options['defaultTransactionOptions']['readPreference'] = + (new \ReflectionClass('\MongoDB\Driver\ReadPreference')) + ->newInstanceArgs( + $options['defaultTransactionOptions']['readPreference'] + ) + ; + } } } } @@ -97,7 +113,8 @@ public static function prepareOptions(&$options){ * @see https://www.php.net/manual/en/mongodb-driver-session.getlogicalsessionid.php * @return string */ - public function getId(){ + public function getId() + { return $this->mongoSession->getLogicalSessionId()->id->jsonSerialize()['$binary']; } @@ -108,17 +125,20 @@ public function getId(){ * @see https://www.php.net/manual/en/mongodb-driver-manager.startsession.php#refsect1-mongodb-driver-manager.startsession-parameters * @return ClientSession returns new session base on a session options for the given connection */ - public static function start($db, $sessionOptions = []){ + public static function start($db, $sessionOptions = []) + { self::prepareOptions($sessionOptions); - if($db->enableProfiling) + if ($db->enableProfiling) { Yii::debug('Starting mongodb session ...', __METHOD__); + } $db->trigger(Connection::EVENT_START_SESSION); $newSession = new self([ 'db' => $db, 'mongoSession' => $db->manager->startSession($sessionOptions), ]); - if($db->enableProfiling) + if ($db->enableProfiling) { Yii::debug('MongoDB session started.', __METHOD__); + } return $newSession; } @@ -126,9 +146,11 @@ public static function start($db, $sessionOptions = []){ * Gets a current transaction of session or creates a new transaction once * @return Transaction returns current transaction */ - public function getTransaction(){ - if($this->_transaction === null) + public function getTransaction() + { + if ($this->_transaction === null) { return $this->_transaction = new Transaction(['clientSession' => $this]); + } return $this->_transaction; } @@ -136,15 +158,17 @@ public function getTransaction(){ * Returns true if the transaction is in progress * @return bool */ - public function getInTransaction(){ + public function getInTransaction() + { return $this->mongoSession->isInTransaction(); } /** * Ends the current session. */ - public function end(){ + public function end() + { $this->mongoSession->endSession(); $this->db->trigger(Connection::EVENT_END_SESSION); } -} +} \ No newline at end of file diff --git a/src/Command.php b/src/Command.php index 4ca90698b..99f8754cf 100644 --- a/src/Command.php +++ b/src/Command.php @@ -82,14 +82,16 @@ class Command extends BaseObject * prepares execOptions for some purposes * @param array|object by reference see Connection::prepareExceOptions */ - private function prepareExecOptions(&$execOptions){ + private function prepareExecOptions(&$execOptions) + { $execOptions = empty($execOptions) ? $this->globalExecOptions : $execOptions; self::prepareCPOptions($execOptions); - #convert session option - if(array_key_exists('session',$execOptions) && $execOptions['session'] instanceof ClientSession) + //Convert session option + if (array_key_exists('session', $execOptions) && $execOptions['session'] instanceof ClientSession) { $execOptions['session'] = $execOptions['session']->mongoSession; + } } /** @@ -101,26 +103,29 @@ private function prepareExecOptions(&$execOptions){ * ['writeConcern' => ['majority',true]] > ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority',true)] * ['readPreference' => 'snapshot'] > ['readPreference' => new \MongoDB\Driver\ReadPreference('primary')] */ - public static function prepareCPOptions(&$options){ - - #convert readConcern option - if(array_key_exists('readConcern', $options) && is_string($options['readConcern'])) + public static function prepareCPOptions(&$options) + { + //Convert readConcern option + if (array_key_exists('readConcern', $options) && is_string($options['readConcern'])) { $options['readConcern'] = new ReadConcern($options['readConcern']); + } - #convert writeConcern option - if(array_key_exists('writeConcern', $options)){ - if(is_string($options['writeConcern']) || is_int($options['writeConcern'])) + //Convert writeConcern option + if (array_key_exists('writeConcern', $options)) { + if (is_string($options['writeConcern']) || is_int($options['writeConcern'])) { $options['writeConcern'] = new WriteConcern($options['writeConcern']); - elseif(is_array($options['writeConcern'])) - $options['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($options['writeConcern']); + } elseif (is_array($options['writeConcern'])) { + $options['writeConcern'] = (new \ReflectionClass('\MongoDB\Driver\WriteConcern'))->newInstanceArgs($options['writeConcern']); + } } - #conver readPreference option - if(array_key_exists('readPreference', $options)){ - if(is_string($options['readPreference'])) + //Convert readPreference option + if (array_key_exists('readPreference', $options)) { + if (is_string($options['readPreference'])) { $options['readPreference'] = new ReadPreference($options['readPreference']); - elseif(is_array($options['readPreference'])) + } elseif (is_array($options['readPreference'])) { $options['readPreference'] = (new \ReflectionClass('\MongoDB\Driver\ReadPreference'))->newInstanceArgs($options['readPreference']); + } } } @@ -269,7 +274,6 @@ public function query($collectionName, $options = [], $execOptions = []) public function dropDatabase($execOptions = []) { $this->document = $this->db->getQueryBuilder()->dropDatabase(); - $result = current($this->execute($execOptions)->toArray()); return $result['ok'] > 0; } @@ -284,7 +288,6 @@ public function dropDatabase($execOptions = []) public function createCollection($collectionName, array $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->createCollection($collectionName, $options); - $result = current($this->execute($execOptions)->toArray()); return $result['ok'] > 0; } @@ -298,7 +301,6 @@ public function createCollection($collectionName, array $options = [], $execOpti public function dropCollection($collectionName, $execOptions = []) { $this->document = $this->db->getQueryBuilder()->dropCollection($collectionName); - $result = current($this->execute($execOptions)->toArray()); return $result['ok'] > 0; } @@ -323,7 +325,6 @@ public function dropCollection($collectionName, $execOptions = []) public function createIndexes($collectionName, $indexes, $execOptions = []) { $this->document = $this->db->getQueryBuilder()->createIndexes($this->databaseName, $collectionName, $indexes); - $result = current($this->execute($execOptions)->toArray()); return $result['ok'] > 0; } @@ -338,7 +339,6 @@ public function createIndexes($collectionName, $indexes, $execOptions = []) public function dropIndexes($collectionName, $indexes, $execOptions = []) { $this->document = $this->db->getQueryBuilder()->dropIndexes($collectionName, $indexes); - return current($this->execute($execOptions)->toArray()); } @@ -383,7 +383,6 @@ public function listIndexes($collectionName, $options = [], $execOptions = []) public function count($collectionName, $condition = [], $options = [], $execOptions = []) { $this->document = $this->db->getQueryBuilder()->count($collectionName, $condition, $options); - $result = current($this->execute($execOptions)->toArray()); return $result['n']; } diff --git a/src/Connection.php b/src/Connection.php index 787c33dc7..7c4959124 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -464,11 +464,14 @@ public function registerFileStreamWrapper($force = false) * @param array $execOptions see docs of Command::execute() and Command::executeBatch() and Command::query() * @return $this */ - public function execOptions($execOptions){ - if(empty($execOptions)) + public function execOptions($execOptions) + { + if (empty($execOptions)) { $this->globalExecOptions = []; - else + } + else { $this->globalExecOptions = array_replace_recursive($this->globalExecOptions, $execOptions); + } return $this; } @@ -477,10 +480,12 @@ public function execOptions($execOptions){ * @param array $sessionOptions see doc of ClientSession::start() * return ClientSession */ - public function startSession($sessionOptions = []){ + public function startSession($sessionOptions = []) + { - if($this->getInSession()) + if ($this->getInSession()) { $this->getSession()->end(); + } $newSession = $this->newSession($sessionOptions); $this->setSession($newSession); @@ -492,9 +497,11 @@ public function startSession($sessionOptions = []){ * @param array $sessionOptions see doc of ClientSession::start() * return ClientSession */ - public function startSessionOnce($sessionOptions = []){ - if($this->getInSession()) + public function startSessionOnce($sessionOptions = []) + { + if ($this->getInSession()) { return $this->getSession(); + } return $this->startSession($sessionOptions); } @@ -503,7 +510,8 @@ public function startSessionOnce($sessionOptions = []){ * @param array $sessionOptions see doc of ClientSession::start() * return ClientSession */ - public function newSession($sessionOptions = []){ + public function newSession($sessionOptions = []) + { return ClientSession::start($this, $sessionOptions); } @@ -511,7 +519,8 @@ public function newSession($sessionOptions = []){ * Checks whether the current connection is in session. * return bool */ - public function getInSession(){ + public function getInSession() + { return array_key_exists('session',$this->globalExecOptions); } @@ -519,7 +528,8 @@ public function getInSession(){ * Checks that the current connection is in session and transaction * return bool */ - public function getInTransaction(){ + public function getInTransaction() + { return $this->getInSession() && $this->getSession()->getInTransaction(); } @@ -527,18 +537,22 @@ public function getInTransaction(){ * Throws custom error if transaction is not ready in connection * @param string $operation a custom message to be shown */ - public function transactionReady($operation){ - if(!$this->getInSession()) - throw new Exception('You can\'t '.$operation.' because current connection is\'t in a session.'); - if(!$this->getSession()->getInTransaction()) - throw new Exception('You can\'t '.$operation.' because transaction not started in current session.'); + public function transactionReady($operation) + { + if (!$this->getInSession()) { + throw new Exception('You can\'t ' . $operation . ' because current connection is\'t in a session.'); + } + if (!$this->getSession()->getInTransaction()) { + throw new Exception('You can\'t ' . $operation . ' because transaction not started in current session.'); + } } /** * Returns current session * return ClientSession|null */ - public function getSession(){ + public function getSession() + { return $this->getInSession() ? $this->globalExecOptions['session'] : null; } @@ -551,7 +565,8 @@ public function getSession(){ * @param array $sessionOptions see doc of ClientSession::start() * return ClientSession */ - public function startTransaction($transactionOptions = [], $sessionOptions = []){ + public function startTransaction($transactionOptions = [], $sessionOptions = []) + { $session = $this->startSession($sessionOptions,true); $session->getTransaction()->start($transactionOptions); return $session; @@ -563,16 +578,19 @@ public function startTransaction($transactionOptions = [], $sessionOptions = []) * @param array $sessionOptions see doc of ClientSession::start() * return ClientSession */ - public function startTransactionOnce($transactionOptions = [], $sessionOptions = []){ - if($this->getInTransaction()) + public function startTransactionOnce($transactionOptions = [], $sessionOptions = []) + { + if ($this->getInTransaction()) { return $this->getSession(); + } return $this->startTransaction($transactionOptions,$sessionOptions); } /** * Commits transaction in current session */ - public function commitTransaction(){ + public function commitTransaction() + { $this->transactionReady('commit transaction'); $this->getSession()->transaction->commit(); } @@ -580,7 +598,8 @@ public function commitTransaction(){ /** * Rollbacks transaction in current session */ - public function rollBackTransaction(){ + public function rollBackTransaction() + { $this->transactionReady('roll back transaction'); $this->getSession()->transaction->rollBack(); } @@ -590,12 +609,15 @@ public function rollBackTransaction(){ * @param ClientSession|null $clientSession new instance of ClientSession to replace * return $this */ - public function setSession($clientSession){ + public function setSession($clientSession) + { #drop session - if(empty($clientSession)) + if (empty($clientSession)) { unset($this->globalExecOptions['session']); - else + } + else { $this->globalExecOptions['session'] = $clientSession; + } return $this; } @@ -606,20 +628,25 @@ public function setSession($clientSession){ * @param array $transactionOptions see doc of Transaction::start() * @param array $sessionOptions see doc of ClientSession::start() */ - public function transaction(callable $actions, $transactionOptions = [], $sessionOptions = []){ + public function transaction(callable $actions, $transactionOptions = [], $sessionOptions = []) + { $session = $this->startTransaction($transactionOptions, $sessionOptions); $success = false; try { $result = call_user_func($actions, $session); - if($session->getTransaction()->getIsActive()) - if($result === false) + if ($session->getTransaction()->getIsActive()) { + if ($result === false) { $session->getTransaction()->rollBack(); - else + } + else { $session->getTransaction()->commit(); + } + } $success = true; } finally { - if(!$success && $session->getTransaction()->getIsActive()) + if (!$success && $session->getTransaction()->getIsActive()) { $session->getTransaction()->rollBack(); + } } } @@ -630,11 +657,14 @@ public function transaction(callable $actions, $transactionOptions = [], $sessio * @param array $transactionOptions see doc of Transaction::start() * @param array $sessionOptions see doc of ClientSession::start() */ - public function transactionOnce(callable $actions, $transactionOptions = [], $sessionOptions = []){ - if($this->getInTransaction()) + public function transactionOnce(callable $actions, $transactionOptions = [], $sessionOptions = []) + { + if ($this->getInTransaction()) { $actions(); - else + } + else { $this->transaction($actions,$transactionOptions,$sessionOptions); + } } /** @@ -642,7 +672,8 @@ public function transactionOnce(callable $actions, $transactionOptions = [], $se * @param callable $actions your block of code must be runned out of session and transaction * @return mixed returns a result of $actions() */ - public function noTransaction(callable $actions){ + public function noTransaction(callable $actions) + { $lastSession = $this->getSession(); $this->setSession(null); try { diff --git a/src/Transaction.php b/src/Transaction.php index 6b2d23ff1..7f817b7f9 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -44,9 +44,11 @@ class Transaction extends \yii\base\BaseObject * @var string $message please see $this->yiiDebug() * @var string $category please see $this->yiiDebug() */ - protected function yiiDebug($message, $category = 'mongodb'){ - if($this->clientSession->db->enableLogging) + protected function yiiDebug($message, $category = 'mongodb') + { + if ($this->clientSession->db->enableLogging) { yii::debug($message,$category); + } } /** @@ -54,9 +56,11 @@ protected function yiiDebug($message, $category = 'mongodb'){ * @var string $token please see $this->yiiBeginProfile() * @var string $category please see $this->yiiBeginProfile() */ - protected function yiiBeginProfile($token, $category = 'mongodb'){ - if($this->clientSession->db->enableProfiling) + protected function yiiBeginProfile($token, $category = 'mongodb') + { + if ($this->clientSession->db->enableProfiling) { yii::beginProfile($token,$category); + } } /** @@ -64,15 +68,18 @@ protected function yiiBeginProfile($token, $category = 'mongodb'){ * @var string $token please see $this->yiiEndProfile() * @var string $category please see $this->yiiEndProfile() */ - protected function yiiEndProfile($token, $category = 'mongodb'){ - if($this->clientSession->db->enableProfiling) + protected function yiiEndProfile($token, $category = 'mongodb') + { + if ($this->clientSession->db->enableProfiling) { yii::endProfile($token,$category); + } } /** * Returns the transaction state. */ - public function getState(){ + public function getState() + { return $this->clientSession->mongoSession->getTransactionState(); } @@ -81,7 +88,8 @@ public function getState(){ * @return bool whether this transaction is active. Only an active transaction * can [[commit()]] or [[rollBack()]]. */ - public function getIsActive(){ + public function getIsActive() + { return $this->clientSession->db->getIsActive() && $this->clientSession->getInTransaction(); } @@ -93,13 +101,15 @@ public function getIsActive(){ * if set when starting the session with ClientSession::start(). * @see https://www.php.net/manual/en/mongodb-driver-session.starttransaction.php#refsect1-mongodb-driver-session.starttransaction-parameters */ - public function start($transactionOptions = []){ + public function start($transactionOptions = []) + { Command::prepareCPOptions($transactionOptions); $this->yiiDebug('Starting mongodb transaction ...', __METHOD__); - if($this->clientSession->getInTransaction()) + if ($this->clientSession->getInTransaction()) { throw new Exception('Nested transaction not supported'); + } $this->clientSession->db->trigger(Connection::EVENT_START_TRANSACTION); - $this->yiiBeginProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); + $this->yiiBeginProfile('mongodb > start transaction(session id => ' . $this->clientSession->getId() . ')'); $this->clientSession->mongoSession->startTransaction($transactionOptions); $this->yiiDebug('MongoDB transaction started.', __METHOD__); } @@ -108,10 +118,11 @@ public function start($transactionOptions = []){ * Commit a transaction. * @see https://www.php.net/manual/en/mongodb-driver-session.committransaction.php */ - public function commit(){ + public function commit() + { $this->yiiDebug('Committing mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->commitTransaction(); - $this->yiiEndProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); + $this->yiiEndProfile('mongodb > start transaction(session id => ' . $this->clientSession->getId() . ')'); $this->yiiDebug('Commit mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); } @@ -120,10 +131,11 @@ public function commit(){ * Rolls back a transaction. * @see https://www.php.net/manual/en/mongodb-driver-session.aborttransaction.php */ - public function rollBack(){ + public function rollBack() + { $this->yiiDebug('Rolling back mongodb transaction ...', __METHOD__); $this->clientSession->mongoSession->abortTransaction(); - $this->yiiEndProfile('mongodb > start transaction(session id => '.$this->clientSession->getId().')'); + $this->yiiEndProfile('mongodb > start transaction(session id => ' . $this->clientSession->getId() . ')'); $this->yiiDebug('Roll back mongodb transaction.', __METHOD__); $this->clientSession->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); } From fc27d61683c808114f7b26d28af26f4fa697efe6 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Fri, 1 Jul 2022 13:47:12 +0430 Subject: [PATCH 73/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index cb2ab9b5f..d095af8eb 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -407,7 +407,7 @@ protected function updateInternal($attributes = null) */ public function delete() { - if(!$this->isTransactional(self::OP_DELETE)) { + if (!$this->isTransactional(self::OP_DELETE)) { return $this->deleteInternal(); } From 1534ea3791a85530cb94e360c27ce136e60f1a22 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Fri, 1 Jul 2022 13:52:23 +0430 Subject: [PATCH 74/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index d095af8eb..c54b737aa 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -518,7 +518,7 @@ private function dumpBsonObject(Type $object) } /** - * Locks a document of the collection in a transaction(like `select for update` feature in mysql) + * Locks a document of the collection in a transaction (like `select for update` feature in MySQL) * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions * @param mixed $id a document id(primary key > _id) * @param string $lockFieldName The name of the field you want to lock. From 853814b8e15c0a802c6bf31962b4b585c5ee1ff7 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Fri, 1 Jul 2022 14:07:29 +0430 Subject: [PATCH 75/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index c54b737aa..25468320a 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -520,7 +520,7 @@ private function dumpBsonObject(Type $object) /** * Locks a document of the collection in a transaction (like `select for update` feature in MySQL) * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions - * @param mixed $id a document id(primary key > _id) + * @param mixed $id a document id (primary key > _id) * @param string $lockFieldName The name of the field you want to lock. * @param array $modifyOptions list of the options in format: optionName => optionValue. * @param Connection $db the Mongo connection uses it to execute the query. From 2f258bfc34cacbd7bbb949f9746b802aa3f77387 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Fri, 1 Jul 2022 14:08:38 +0430 Subject: [PATCH 76/82] Update src/ActiveRecord.php Co-authored-by: Alexander Makarov --- src/ActiveRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 25468320a..4685ea00e 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -547,7 +547,7 @@ public static function LockDocument($id, $lockFieldName, $modifyOptions = [], $d /** * Locking a document in stubborn mode on a transaction (like `select for update` feature in MySQL) * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions - * notice : you can not use stubborn mode if transaction is started in current session(or use your session with `mySession` parameter). + * notice : you can not use stubborn mode if transaction is started in current session (or use your session with `mySession` parameter). * @param mixed $id a document id(primary key > _id) * @param array $options list of options in format: * [ From 39da9a6b7c2fd43e34d460d225c192dfa653c485 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Fri, 1 Jul 2022 14:10:40 +0430 Subject: [PATCH 77/82] Update src/Transaction.php Co-authored-by: Alexander Makarov --- src/Transaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transaction.php b/src/Transaction.php index 7f817b7f9..04c824487 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -47,7 +47,7 @@ class Transaction extends \yii\base\BaseObject protected function yiiDebug($message, $category = 'mongodb') { if ($this->clientSession->db->enableLogging) { - yii::debug($message,$category); + Yii::debug($message,$category); } } From 37cdc635b3784a9b5a22af135b1e2b7aa7c347aa Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Fri, 1 Jul 2022 14:11:24 +0430 Subject: [PATCH 78/82] Update src/Transaction.php Co-authored-by: Alexander Makarov --- src/Transaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transaction.php b/src/Transaction.php index 04c824487..75e60bdaf 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -59,7 +59,7 @@ protected function yiiDebug($message, $category = 'mongodb') protected function yiiBeginProfile($token, $category = 'mongodb') { if ($this->clientSession->db->enableProfiling) { - yii::beginProfile($token,$category); + Yii::beginProfile($token,$category); } } From 6f607d7856d9a00b822b181a5790eff755d8a21d Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Fri, 1 Jul 2022 14:11:33 +0430 Subject: [PATCH 79/82] Update src/Transaction.php Co-authored-by: Alexander Makarov --- src/Transaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transaction.php b/src/Transaction.php index 75e60bdaf..2ef14504e 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -71,7 +71,7 @@ protected function yiiBeginProfile($token, $category = 'mongodb') protected function yiiEndProfile($token, $category = 'mongodb') { if ($this->clientSession->db->enableProfiling) { - yii::endProfile($token,$category); + Yii::endProfile($token,$category); } } From 412a8ee9b8b05116c42824a4b9ba2f69ff4bb510 Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Fri, 1 Jul 2022 15:32:14 +0430 Subject: [PATCH 80/82] Clean code & better development --- src/ClientSession.php | 3 +- src/Collection.php | 36 ++++++++------- src/Command.php | 101 ++++++++++++++++++++++++++---------------- src/Connection.php | 32 ++++++++----- src/Transaction.php | 2 +- 5 files changed, 108 insertions(+), 66 deletions(-) diff --git a/src/ClientSession.php b/src/ClientSession.php index 9ad7db123..738ec17fc 100644 --- a/src/ClientSession.php +++ b/src/ClientSession.php @@ -121,8 +121,7 @@ public function getId() /** * Starts a new mongodb session in a connection. * @param Connection $db - * @param Array $sessionOptions Creates a ClientSession for the given options - * @see https://www.php.net/manual/en/mongodb-driver-manager.startsession.php#refsect1-mongodb-driver-manager.startsession-parameters + * @param Array $sessionOptions Creates a ClientSession for the given options {@see https://www.php.net/manual/en/mongodb-driver-manager.startsession.php#refsect1-mongodb-driver-manager.startsession-parameters} * @return ClientSession returns new session base on a session options for the given connection */ public static function start($db, $sessionOptions = []) diff --git a/src/Collection.php b/src/Collection.php index f6ea975c6..02fd1702e 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -56,7 +56,7 @@ public function getFullName() /** * Drops this collection. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see Command::dropCollection()} * @throws Exception on failure. * @return bool whether the operation successful. */ @@ -69,7 +69,7 @@ public function drop($execOptions = []) * Returns the list of defined indexes. * @return array list of indexes info. * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see Command::listIndexes()} * @since 2.1 */ public function listIndexes($options = [], $execOptions = []) @@ -109,7 +109,7 @@ public function listIndexes($options = [], $execOptions = []) * * See [[https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#options-for-all-index-types]] * for the full list of options. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see Command::createIndexes()} * @return bool whether operation was successful. * @since 2.1 */ @@ -122,7 +122,7 @@ public function createIndexes($indexes, $execOptions = []) * Drops collection indexes by name. * @param string $indexes wildcard for name of the indexes to be dropped. * You can use `*` to drop all indexes. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see Command::dropIndexes()} * @return int count of dropped indexes. */ public function dropIndexes($indexes, $execOptions = []) @@ -148,7 +148,7 @@ public function dropIndexes($indexes, $execOptions = []) * ``` * * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see Command::createIndexes()} * @throws Exception on failure. * @return bool whether the operation successful. */ @@ -176,7 +176,7 @@ public function createIndex($columns, $options = [], $execOptions = []) * ] * ``` * - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see Command::dropIndexes()} * @throws Exception on failure. * @return bool whether the operation successful. */ @@ -206,7 +206,7 @@ public function dropIndex($columns, $execOptions = []) /** * Drops all indexes for this collection. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see Command::dropIndexes()} * @throws Exception on failure. * @return int count of dropped indexes. */ @@ -222,7 +222,7 @@ public function dropAllIndexes($execOptions = []) * @param array $condition query condition * @param array $fields fields to be selected * @param array $options query options (available since 2.1). - * @param array $execOptions -> goto Command::executeQuery() + * @param array $execOptions {@see Command::find()} * @return \MongoDB\Driver\Cursor cursor for the search results * @see Query */ @@ -239,7 +239,7 @@ public function find($condition = [], $fields = [], $options = [], $execOptions * @param array $condition query condition * @param array $fields fields to be selected * @param array $options query options (available since 2.1). - * @param array $execOptions -> goto Command::executeQuery() + * @param array $execOptions {@see find()} * @return array|null the single document. Null is returned if the query results in nothing. */ public function findOne($condition = [], $fields = [], $options = [], $execOptions = []) @@ -255,6 +255,7 @@ public function findOne($condition = [], $fields = [], $options = [], $execOptio * @param array $condition query condition * @param array $update update criteria * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions {@see Command::findAndModify()} * @return array|null the original document, or the modified document when $options['new'] is set. * @throws Exception on failure. */ @@ -268,7 +269,7 @@ public function findAndModify($condition, $update, $options = [], $execOptions = * @param array|object $data data to be inserted. * @param array $options list of options in format: optionName => optionValue. * @return \MongoDB\BSON\ObjectID new record ID instance. - * @param array $execOptions -> goto Command::executeBatch() + * @param array $execOptions {@see Command::insert()} * @throws Exception on failure. */ public function insert($data, $options = [], $execOptions = []) @@ -280,11 +281,11 @@ public function insert($data, $options = [], $execOptions = []) * Inserts several new rows into collection. * @param array $rows array of arrays or objects to be inserted. * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::executeBatch() + * @param array $execOptions {@see Command::batchInsert()} * @return array inserted data, each row will have "_id" key assigned to it. * @throws Exception on failure. */ - public function batchInsert($rows, $options = [], $execOptions = []) + public function batchInsert($rows, $options = [], $r = []) { $insertedIds = $this->database->createCommand()->batchInsert($this->name, $rows, $options, $execOptions); foreach ($rows as $key => $row) { @@ -300,7 +301,7 @@ public function batchInsert($rows, $options = [], $execOptions = []) * @param array $condition description of the objects to update. * @param array $newData the object with which to update the matching records. * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::executeBatch() + * @param array $execOptions {@see Command::update()} * @return int|bool number of updated documents or whether operation was successful. * @throws Exception on failure. */ @@ -315,7 +316,7 @@ public function update($condition, $newData, $options = [], $execOptions = []) * @param array|object $data data to be updated/inserted. * @param array $options list of options in format: optionName => optionValue. * @return \MongoDB\BSON\ObjectID updated/new record id instance. - * @param array $execOptions -> goto Command::executeBatch() + * @param array $execOptions {@see Command::insert()} * @throws Exception on failure. */ public function save($data, $options = [], $execOptions = []) @@ -334,7 +335,7 @@ public function save($data, $options = [], $execOptions = []) * Removes data from the collection. * @param array $condition description of records to remove. * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::executeBatch() + * @param array $execOptions {@see Command::delete()} * @return int|bool number of updated documents or whether operation was successful. * @throws Exception on failure. */ @@ -349,6 +350,7 @@ public function remove($condition = [], $options = [], $execOptions = []) * Counts records in this collection. * @param array $condition query condition * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions {@see Command::count()} * @return int records count. * @since 2.1 */ @@ -362,6 +364,7 @@ public function count($condition = [], $options = [], $execOptions = []) * @param string $column column to use. * @param array $condition query parameters. * @param array $options list of options in format: optionName => optionValue. + * @param array $execOptions {@see Command::distinct()} * @return array|bool array of distinct values, or "false" on failure. * @throws Exception on failure. */ @@ -376,6 +379,7 @@ public function distinct($column, $condition = [], $options = [], $execOptions = * otherwise - an array of aggregation results. * @param array $pipelines list of pipeline operators. * @param array $options optional parameters. + * @param array $execOptions {@see Command::aggregate()} * @return array|\MongoDB\Driver\Cursor the result of the aggregation. * @throws Exception on failure. */ @@ -396,6 +400,7 @@ public function aggregate($pipelines, $options = [], $execOptions = []) * @param array $options optional parameters to the group command. Valid options include: * - condition - criteria for including a document in the aggregation. * - finalize - function called once per unique key that takes the final output of the reduce function. + * @param array $execOptions {@see Command::group()} * @return array the result of the aggregation. * @throws Exception on failure. */ @@ -440,6 +445,7 @@ public function group($keys, $initial, $reduce, $options = [], $execOptions = [] * - jsMode: bool, specifies whether to convert intermediate data into BSON format between the execution of the map and reduce functions. * - verbose: bool, specifies whether to include the timing information in the result information. * + * @param array $execOptions {@see Command::mapReduce()} * @return string|array the map reduce output collection name or output results. * @throws Exception on failure. */ diff --git a/src/Command.php b/src/Command.php index 99f8754cf..f8c621174 100644 --- a/src/Command.php +++ b/src/Command.php @@ -80,30 +80,53 @@ class Command extends BaseObject /** * prepares execOptions for some purposes - * @param array|object by reference see Connection::prepareExceOptions + * @param array|object|null $execOptions {@see prepareManagerOptions()} */ - private function prepareExecOptions(&$execOptions) + private function prepareExecCommandOptions(&$execOptions) { - $execOptions = empty($execOptions) ? $this->globalExecOptions : $execOptions; + if (empty($execOptions)) { + $execOptions = array_merge($this->globalExecOptions['command'],$this->globalExecOptions['share']); + } + self::prepareManagerOptions($execOptions); + } - self::prepareCPOptions($execOptions); + /** + * prepares execOptions for some purposes + * @param array|object|null $execOptions {@see prepareManagerOptions()} + */ + private function prepareExecBulkWriteOptions(&$execOptions) + { + if (empty($execOptions)) { + $execOptions = array_merge($this->globalExecOptions['bulkWrite'],$this->globalExecOptions['share']); + } + self::prepareManagerOptions($execOptions); + } - //Convert session option - if (array_key_exists('session', $execOptions) && $execOptions['session'] instanceof ClientSession) { - $execOptions['session'] = $execOptions['session']->mongoSession; + /** + * prepares execOptions for some purposes + * @param array|object|null $execOptions {@see prepareManagerOptions()} + */ + private function prepareExecQueryOptions(&$execOptions) + { + if (empty($execOptions)) { + $execOptions = array_merge($this->globalExecOptions['query'],$this->globalExecOptions['share']); } + self::prepareManagerOptions($execOptions); } /** * preapare Concern and Preference options for easy use - * @param array|object by reference + * @param array|object $options by reference * convert string option to object * ['readConcern' => 'snapshot'] > ['readConcern' => new \MongoDB\Driver\ReadConcern('snapshot')] * ['writeConcern' => 'majority'] > ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority')] * ['writeConcern' => ['majority',true]] > ['writeConcern' => new \MongoDB\Driver\WriteConcern('majority',true)] * ['readPreference' => 'snapshot'] > ['readPreference' => new \MongoDB\Driver\ReadPreference('primary')] + * {@see https://www.php.net/manual/en/mongodb-driver-manager.executecommand.php#refsect1-mongodb-driver-manager.executecommand-parameters} + * {@see https://www.php.net/manual/en/mongodb-driver-manager.executebulkwrite.php#refsect1-mongodb-driver-manager.executebulkwrite-parameters} + * {@see https://www.php.net/manual/en/mongodb-driver-server.executequery.php#refsect1-mongodb-driver-server.executequery-parameters} */ - public static function prepareCPOptions(&$options) + public static function prepareManagerOptions(&$options) { //Convert readConcern option if (array_key_exists('readConcern', $options) && is_string($options['readConcern'])) { @@ -127,11 +150,16 @@ public static function prepareCPOptions(&$options) $options['readPreference'] = (new \ReflectionClass('\MongoDB\Driver\ReadPreference'))->newInstanceArgs($options['readPreference']); } } - } + + //Convert session option + if (array_key_exists('session', $options) && $options['session'] instanceof ClientSession) { + $options['session'] = $options['session']->mongoSession; + } + } /** * Executes this command. - * @param array $execOptions options for executeCommand + * @param array $execOptions (@see prepareExecCommandOptions()) * Note: "readConcern" and "writeConcern" options will not default to corresponding values from the MongoDB * Connection URI nor will the MongoDB server version be taken into account * @see https://www.php.net/manual/en/mongodb-driver-server.executebulkwrite.php#refsect1-mongodb-driver-server.executebulkwrite-parameters @@ -140,7 +168,7 @@ public static function prepareCPOptions(&$options) */ public function execute($execOptions = []) { - $this->prepareExecOptions($execOptions); + $this->prepareExecCommandOptions($execOptions); $databaseName = $this->databaseName === null ? $this->db->defaultDatabaseName : $this->databaseName; @@ -167,7 +195,7 @@ public function execute($execOptions = []) * Execute commands batch (bulk). * @param string $collectionName collection name. * @param array $options batch options. - * @param array $execOptions options for executeBulkWrite + * @param array $execOptions (@see prepareExecBulkWriteOptions()) * @return array array of 2 elements: * * - 'insertedIds' - contains inserted IDs. @@ -179,7 +207,7 @@ public function execute($execOptions = []) */ public function executeBatch($collectionName, $options = [], $execOptions = []) { - $this->prepareExecOptions($execOptions); + $this->prepareExecBulkWriteOptions($execOptions); $databaseName = $this->databaseName === null ? $this->db->defaultDatabaseName : $this->databaseName; @@ -226,14 +254,13 @@ public function executeBatch($collectionName, $options = [], $execOptions = []) * Executes this command as a mongo query * @param string $collectionName collection name * @param array $options query options. - * @param array $execOptions options for executeQuery - * @see https://www.php.net/manual/en/mongodb-driver-server.executequery.php#refsect1-mongodb-driver-server.executequery-parameters + * @param array $execOptions (@see prepareExecQueryOptions()) * @return \MongoDB\Driver\Cursor result cursor. * @throws Exception on failure */ public function query($collectionName, $options = [], $execOptions = []) { - $this->prepareExecOptions($execOptions); + $this->prepareExecQueryOptions($execOptions); $databaseName = $this->databaseName === null ? $this->db->defaultDatabaseName : $this->databaseName; @@ -268,7 +295,7 @@ public function query($collectionName, $options = [], $execOptions = []) /** * Drops database associated with this command. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return bool whether operation was successful. */ public function dropDatabase($execOptions = []) @@ -282,7 +309,7 @@ public function dropDatabase($execOptions = []) * Creates new collection in database associated with this command.s * @param string $collectionName collection name * @param array $options collection options in format: "name" => "value" - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return bool whether operation was successful. */ public function createCollection($collectionName, array $options = [], $execOptions = []) @@ -295,7 +322,7 @@ public function createCollection($collectionName, array $options = [], $execOpti /** * Drops specified collection. * @param string $collectionName name of the collection to be dropped. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return bool whether operation was successful. */ public function dropCollection($collectionName, $execOptions = []) @@ -319,7 +346,7 @@ public function dropCollection($collectionName, $execOptions = []) * * See [[https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#options-for-all-index-types]] * for the full list of options. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return bool whether operation was successful. */ public function createIndexes($collectionName, $indexes, $execOptions = []) @@ -333,7 +360,7 @@ public function createIndexes($collectionName, $indexes, $execOptions = []) * Drops collection indexes by name. * @param string $collectionName collection name. * @param string $indexes wildcard for name of the indexes to be dropped. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return array result data. */ public function dropIndexes($collectionName, $indexes, $execOptions = []) @@ -346,7 +373,7 @@ public function dropIndexes($collectionName, $indexes, $execOptions = []) * Returns information about current collection indexes. * @param string $collectionName collection name * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return array list of indexes info. * @throws Exception on failure. */ @@ -377,7 +404,7 @@ public function listIndexes($collectionName, $options = [], $execOptions = []) * @param string $collectionName collection name * @param array $condition filter condition * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return int records count */ public function count($collectionName, $condition = [], $options = [], $execOptions = []) @@ -458,7 +485,7 @@ public function addDelete($condition, $options = []) * @param string $collectionName collection name * @param array $document document content * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::executeBatch() + * @param array $execOptions {@see executeBatch()} * @return ObjectID|bool inserted record ID, `false` - on failure. */ public function insert($collectionName, $document, $options = [], $execOptions = []) @@ -479,7 +506,7 @@ public function insert($collectionName, $document, $options = [], $execOptions = * @param string $collectionName collection name * @param array[] $documents documents list * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::executeBatch() + * @param array $execOptions {@see executeBatch()} * @return array|false list of inserted IDs, `false` on failure. */ public function batchInsert($collectionName, $documents, $options = [], $execOptions = []) @@ -507,7 +534,7 @@ public function batchInsert($collectionName, $documents, $options = [], $execOpt * @param array $condition filter condition * @param array $document data to be updated. * @param array $options update options. - * @param array $execOptions -> goto Command::executeBatch() + * @param array $execOptions {@see executeBatch()} * @return WriteResult write result. */ public function update($collectionName, $condition, $document, $options = [], $execOptions = []) @@ -532,7 +559,7 @@ public function update($collectionName, $condition, $document, $options = [], $e * @param string $collectionName collection name. * @param array $condition filter condition. * @param array $options delete options. - * @param array $execOptions -> goto Command::executeBatch() + * @param array $execOptions {@see executeBatch()} * @return WriteResult write result. */ public function delete($collectionName, $condition, $options = [], $execOptions = []) @@ -557,7 +584,7 @@ public function delete($collectionName, $condition, $options = [], $execOptions * @param string $collectionName collection name * @param array $condition filter condition * @param array $options query options. - * @param array $execOptions -> goto Command::executeQuery() + * @param array $execOptions {@see query()} * @return \MongoDB\Driver\Cursor result cursor. */ public function find($collectionName, $condition, $options = [], $execOptions = []) @@ -598,7 +625,7 @@ public function find($collectionName, $condition, $options = [], $execOptions = * @param array $condition query condition * @param array $update update criteria * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return array|null the original document, or the modified document when $options['new'] is set. */ public function findAndModify($collectionName, $condition = [], $update = [], $options = [], $execOptions = []) @@ -621,7 +648,7 @@ public function findAndModify($collectionName, $condition = [], $update = [], $o * @param string $fieldName field name to use. * @param array $condition query parameters. * @param array $options list of options in format: optionName => optionValue. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return array array of distinct values, or "false" on failure. */ public function distinct($collectionName, $fieldName, $condition = [], $options = [], $execOptions = []) @@ -651,7 +678,7 @@ public function distinct($collectionName, $fieldName, $condition = [], $options * @param array $options optional parameters to the group command. Valid options include: * - condition - criteria for including a document in the aggregation. * - finalize - function called once per unique key that takes the final output of the reduce function. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return array the result of the aggregation. */ public function group($collectionName, $keys, $initial, $reduce, $options = [], $execOptions = []) @@ -685,7 +712,7 @@ public function group($collectionName, $keys, $initial, $reduce, $options = [], * - jsMode: bool, specifies whether to convert intermediate data into BSON format between the execution of the map and reduce functions. * - verbose: bool, specifies whether to include the timing information in the result information. * - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return string|array the map reduce output collection name or output results. */ public function mapReduce($collectionName, $map, $reduce, $out, $condition = [], $options = [], $execOptions = []) @@ -705,7 +732,7 @@ public function mapReduce($collectionName, $map, $reduce, $out, $condition = [], * @param string $collectionName collection name * @param array $pipelines list of pipeline operators. * @param array $options optional parameters. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return array|\MongoDB\Driver\Cursor aggregation result. */ public function aggregate($collectionName, $pipelines, $options = [], $execOptions = []) @@ -731,7 +758,7 @@ public function aggregate($collectionName, $pipelines, $options = [], $execOptio * Return an explanation of the query, often useful for optimization and debugging. * @param string $collectionName collection name * @param array $query query document. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return array explanation of the query. */ public function explain($collectionName, $query, $execOptions = []) @@ -746,7 +773,7 @@ public function explain($collectionName, $query, $execOptions = []) * Returns the list of available databases. * @param array $condition filter condition. * @param array $options options list. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return array database information */ public function listDatabases($condition = [], $options = [], $execOptions = []) @@ -769,7 +796,7 @@ public function listDatabases($condition = [], $options = [], $execOptions = []) * Returns the list of available collections. * @param array $condition filter condition. * @param array $options options list. - * @param array $execOptions -> goto Command::execute() + * @param array $execOptions {@see execute()} * @return array collections information. */ public function listCollections($condition = [], $options = [], $execOptions = []) diff --git a/src/Connection.php b/src/Connection.php index 7c4959124..c6e5cb657 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -176,9 +176,19 @@ class Connection extends Component public $fileStreamWrapperClass = 'yii\mongodb\file\StreamWrapper'; /** - * @var array default options for `executeCommand` method of MongoDB\Driver\Manager in `Command` class. + * @var array default options for `executeCommand` , executeBulkWrite and executeQuery method of MongoDB\Driver\Manager in `Command` class. */ - public $globalExecOptions = []; + public $globalExecOptions = [ + /** + * Shared between some(or all) methods(executeCommand|executeBulkWrite|executeQuery). + * This options are : + * - session + */ + 'share' => [], + 'command' => [], + 'bulkWrite' => [], + 'query' => [], + ]; /** * @var string name of the MongoDB database to use by default. @@ -460,17 +470,17 @@ public function registerFileStreamWrapper($force = false) } /** - * Sets global execOptions for Command::execute() and Command::executeBatch() and Command::query() - * @param array $execOptions see docs of Command::execute() and Command::executeBatch() and Command::query() + * Recursive replacement on $this->globalExecOptions with new options. {@see $this->globalExecOptions} + * @param array $newExecOptions {@see $this->globalExecOptions} * @return $this */ - public function execOptions($execOptions) + public function execOptions($newExecOptions) { - if (empty($execOptions)) { + if (empty($newExecOptions)) { $this->globalExecOptions = []; } else { - $this->globalExecOptions = array_replace_recursive($this->globalExecOptions, $execOptions); + $this->globalExecOptions = array_replace_recursive($this->globalExecOptions, $newExecOptions); } return $this; } @@ -521,7 +531,7 @@ public function newSession($sessionOptions = []) */ public function getInSession() { - return array_key_exists('session',$this->globalExecOptions); + return array_key_exists('session',$this->globalExecOptions['share']); } /** @@ -553,7 +563,7 @@ public function transactionReady($operation) */ public function getSession() { - return $this->getInSession() ? $this->globalExecOptions['session'] : null; + return $this->getInSession() ? $this->globalExecOptions['share']['session'] : null; } /** @@ -613,10 +623,10 @@ public function setSession($clientSession) { #drop session if (empty($clientSession)) { - unset($this->globalExecOptions['session']); + unset($this->globalExecOptions['share']['session']); } else { - $this->globalExecOptions['session'] = $clientSession; + $this->globalExecOptions['share']['session'] = $clientSession; } return $this; } diff --git a/src/Transaction.php b/src/Transaction.php index 2ef14504e..5c40faeb8 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -103,7 +103,7 @@ public function getIsActive() */ public function start($transactionOptions = []) { - Command::prepareCPOptions($transactionOptions); + Command::prepareManagerOptions($transactionOptions); $this->yiiDebug('Starting mongodb transaction ...', __METHOD__); if ($this->clientSession->getInTransaction()) { throw new Exception('Nested transaction not supported'); From 141a3a639eff68e473ba13fc5357fc3a5481fedf Mon Sep 17 00:00:00 2001 From: Ziaratban Date: Fri, 1 Jul 2022 15:41:16 +0430 Subject: [PATCH 81/82] Update Collection.php --- src/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Collection.php b/src/Collection.php index 02fd1702e..281edc718 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -285,7 +285,7 @@ public function insert($data, $options = [], $execOptions = []) * @return array inserted data, each row will have "_id" key assigned to it. * @throws Exception on failure. */ - public function batchInsert($rows, $options = [], $r = []) + public function batchInsert($rows, $options = [], $execOptions = []) { $insertedIds = $this->database->createCommand()->batchInsert($this->name, $rows, $options, $execOptions); foreach ($rows as $key => $row) { From 49cecb1687d82ff9665a37671ee8b2aeca01a49c Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 1 Jul 2022 22:46:35 +0300 Subject: [PATCH 82/82] Apply suggestions from code review --- src/ActiveRecord.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 4685ea00e..8b5a8cdee 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -548,15 +548,15 @@ public static function LockDocument($id, $lockFieldName, $modifyOptions = [], $d * Locking a document in stubborn mode on a transaction (like `select for update` feature in MySQL) * @see https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions * notice : you can not use stubborn mode if transaction is started in current session (or use your session with `mySession` parameter). - * @param mixed $id a document id(primary key > _id) + * @param mixed $id a document id (primary key > _id) * @param array $options list of options in format: * [ - * 'mySession' => false, #A custom session instance of ClientSession for start a transaction. - * 'transactionOptions' => [], #New transaction options. see $transactionOptions in Transaction::start() - * 'modifyOptions' => [], #See $options in ActiveQuery::modify() - * 'sleep' => 1000000, #A time parameter in microseconds to wait. the default is one second. - * 'try' => 0, #Maximum count of retry. throw write conflict error after reached this value. the zero default is unlimited. - * 'lockFieldName' => '_lock' #The name of the field you want to lock. default is '_lock' + * 'mySession' => false, # A custom session instance of ClientSession for start a transaction. + * 'transactionOptions' => [], # New transaction options. see $transactionOptions in Transaction::start() + * 'modifyOptions' => [], # See $options in ActiveQuery::modify() + * 'sleep' => 1000000, # A time parameter in microseconds to wait. the default is one second. + * 'try' => 0, # Maximum count of retry. throw write conflict error after reached this value. the zero default is unlimited. + * 'lockFieldName' => '_lock' # The name of the field you want to lock. default is '_lock' * ] * @param Connection $db the Mongo connection uses it to execute the query. * @return ActiveRecord|null returns the locked document.