├── .gitignore ├── .idea ├── php.xml ├── misc.xml ├── vcs.xml ├── .gitignore ├── laravel-mongodb-transactions.iml ├── modules.xml └── composerJson.xml ├── src └── Mongodb │ ├── Eloquent │ └── Model.php │ ├── MongodbServiceProvider.php │ ├── Connection.php │ └── Query │ └── Builder.php ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/laravel-mongodb-transactions.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/composerJson.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Mongodb/Eloquent/Model.php: -------------------------------------------------------------------------------- 1 | getConnection(); 15 | 16 | return new QueryBuilder($connection, $connection->getPostProcessor()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imanrjb/laravel-mongodb", 3 | "description": "Extend Jenssegers/laravel-mongodb to support transaction function", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Iman RJB", 9 | "email": "iman.rjb@gmail.com" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "require": { 14 | "jenssegers/mongodb": "^3.8" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "ImanRjb\\": "src/" 19 | } 20 | }, 21 | "extra": { 22 | "laravel": { 23 | "providers": [ 24 | "ImanRjb\\Mongodb\\MongodbServiceProvider" 25 | ] 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/Mongodb/MongodbServiceProvider.php: -------------------------------------------------------------------------------- 1 | app['db']); 15 | 16 | Model::setEventDispatcher($this->app['events']); 17 | } 18 | 19 | public function register() 20 | { 21 | // Add database driver. 22 | $this->app->resolving('db', function ($db) { 23 | $db->extend('mongodb', function ($config, $name) { 24 | $config['name'] = $name; 25 | return new Connection($config); 26 | }); 27 | }); 28 | 29 | // Add connector for queue support. 30 | $this->app->resolving('queue', function ($queue) { 31 | $queue->addConnector('mongodb', function () { 32 | return new MongoConnector($this->app['db']); 33 | }); 34 | }); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Mongodb/Connection.php: -------------------------------------------------------------------------------- 1 | getSession()) { 16 | $this->session = $this->getMongoClient()->startSession(); 17 | $this->session->startTransaction($options); 18 | } 19 | } 20 | 21 | 22 | public function commit() 23 | { 24 | if ($this->getSession()) { 25 | $this->session->commitTransaction(); 26 | $this->clearSession(); 27 | } 28 | } 29 | 30 | 31 | public function rollBack($toLevel = null) 32 | { 33 | if ($this->getSession()) { 34 | $this->session->abortTransaction(); 35 | $this->clearSession(); 36 | } 37 | } 38 | 39 | 40 | protected function clearSession() 41 | { 42 | $this->session = null; 43 | } 44 | 45 | /** 46 | * 47 | * @param string $collection 48 | * @return Builder|\Jenssegers\Mongodb\Query\Builder 49 | * @date 2019-07-22 50 | */ 51 | public function collection($collection) 52 | { 53 | $query = new Query\Builder($this, $this->getPostProcessor()); 54 | 55 | return $query->from($collection); 56 | } 57 | 58 | public function getSession() 59 | { 60 | return $this->session; 61 | } 62 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Mongodb (Transactional support) 2 | 3 | ### Introduction 4 | 5 | 6 | Jensseger's laravel-mongodb extension package is very popular among Laravel developers, but it lacks a transactional feature. mongoDB 4.x supports multi-document transactions. Therefore, this package extends [Jenssegers/laravel-mongodb](https://github.com/jenssegers/laravel-mongodb) with transactional support. 7 | 8 | 1. mongoDB transactions are based on the mongoDB4.x replica set environment. [mongoDB](https://docs.mongodb.com/manual/core/transactions) 9 | 2. This package depends on [Jenssegers/laravel-mongodb](https://packagist.org/packages/jenssegers/mongodb), so it needs to be installed first. 10 | 11 | ### Installation 12 | 13 | Regarding the use of packages, it is necessary to replace [Jenssegers/laravel-mongodb](https://packagist.org/packages/jenssegers/mongodb#installation): 14 | 15 | Install by composer 16 | ```bash 17 | composer require imanrjb/laravel-mongodb 18 | ``` 19 | 20 | Laravel 21 | ```php 22 | //Jenssegers\Mongodb\MongodbServiceProvider::class, 23 | ImanRjb\Mongodb\MongodbServiceProvider::class 24 | ``` 25 | 26 | Lumen 27 | ```php 28 | //$app->register(Jenssegers\Mongodb\MongodbServiceProvider::class); 29 | $app->register(ImanRjb\Mongodb\MongodbServiceProvider::class); 30 | 31 | $app->withEloquent(); 32 | ``` 33 | 34 | Eloquent 35 | -------- 36 | Eloquent only expands on transaction-related content, so it directly replaces [Jenssegers/laravel-mongodb](https://github.com/jenssegers/laravel-mongodb#eloquent) 37 | 38 | ```php 39 | use ImanRjb\Mongodb\Eloquent\Model; 40 | 41 | class User extends Model {} 42 | ``` 43 | 44 | ```php 45 | use ImanRjb\Mongodb\Eloquent\Model; 46 | 47 | class MyModel extends Model { 48 | 49 | protected $connection = 'mongodb'; 50 | 51 | } 52 | ``` 53 | 54 | For more Eloquent documentation see (http://laravel.com/docs/eloquent) 55 | 56 | ### Usage 57 | 58 | ```php 59 | DB::connection('mongodb')->beginTransaction(); 60 | 61 | try { 62 | User::insert($userData); 63 | UserInfo::insert($userInfoData); 64 | 65 | DB::connection('mongodb')->commit(); 66 | } catch (\Exception $e) { 67 | DB::connection('mongodb')->rollBack(); 68 | throw $e; 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /src/Mongodb/Query/Builder.php: -------------------------------------------------------------------------------- 1 | grammar = new Grammar; 18 | $this->connection = $connection; 19 | $this->processor = $processor; 20 | } 21 | 22 | /** 23 | * @inheritdoc 24 | */ 25 | public function aggregate($function, $columns = []) 26 | { 27 | $this->aggregate = compact('function', 'columns'); 28 | 29 | $previousColumns = $this->columns; 30 | 31 | // We will also back up the select bindings since the select clause will be 32 | // removed when performing the aggregate function. Once the query is run 33 | // we will add the bindings back onto this query so they can get used. 34 | $previousSelectBindings = $this->bindings['select']; 35 | 36 | $this->bindings['select'] = []; 37 | 38 | $results = $this->get($columns); 39 | 40 | // Once we have executed the query, we will reset the aggregate property so 41 | // that more select queries can be executed against the database without 42 | // the aggregate value getting in the way when the grammar builds it. 43 | $this->aggregate = null; 44 | $this->columns = $previousColumns; 45 | $this->bindings['select'] = $previousSelectBindings; 46 | 47 | if (isset($results[0])) { 48 | $result = (array) $results[0]; 49 | 50 | return $result['aggregate']; 51 | } 52 | } 53 | 54 | /** 55 | * @inheritdoc 56 | */ 57 | public function insert(array $values) 58 | { 59 | // Since every insert gets treated like a batch insert, we will have to detect 60 | // if the user is inserting a single document or an array of documents. 61 | $batch = true; 62 | 63 | foreach ($values as $value) { 64 | // As soon as we find a value that is not an array we assume the user is 65 | // inserting a single document. 66 | if (!is_array($value)) { 67 | $batch = false; 68 | break; 69 | } 70 | } 71 | 72 | if (!$batch) { 73 | $values = [$values]; 74 | } 75 | 76 | // Batch insert 77 | $options = $this->session(); 78 | $result = $this->collection->insertMany($values, $options); 79 | 80 | return (1 == (int) $result->isAcknowledged()); 81 | } 82 | 83 | /** 84 | * @inheritdoc 85 | */ 86 | public function insertGetId(array $values, $sequence = null) 87 | { 88 | $options = $this->session(); 89 | $result = $this->collection->insertOne($values, $options); 90 | 91 | if (1 == (int) $result->isAcknowledged()) { 92 | if (is_null($sequence)) { 93 | $sequence = '_id'; 94 | } 95 | 96 | // Return id 97 | return $sequence == '_id' ? $result->getInsertedId() : $values[$sequence]; 98 | } 99 | } 100 | 101 | /** 102 | * @inheritdoc 103 | */ 104 | public function update(array $values, array $options = []) 105 | { 106 | // Use $set as default operator. 107 | if (!Str::startsWith(key($values), '$')) { 108 | $values = ['$set' => $values]; 109 | } 110 | $options = $this->session($options); 111 | return $this->performUpdate($values, $options); 112 | } 113 | 114 | /** 115 | * @inheritdoc 116 | */ 117 | public function increment($column, $amount = 1, array $extra = [], array $options = []) 118 | { 119 | $query = ['$inc' => [$column => $amount]]; 120 | 121 | if (!empty($extra)) { 122 | $query['$set'] = $extra; 123 | } 124 | 125 | // Protect 126 | $this->where(function ($query) use ($column) { 127 | $query->where($column, 'exists', false); 128 | 129 | $query->orWhereNotNull($column); 130 | }); 131 | 132 | return $this->performUpdate($query, $options); 133 | } 134 | 135 | /** 136 | * @inheritdoc 137 | */ 138 | public function decrement($column, $amount = 1, array $extra = [], array $options = []) 139 | { 140 | return $this->increment($column, -1 * $amount, $extra, $options); 141 | } 142 | 143 | /** 144 | * @inheritdoc 145 | */ 146 | public function delete($id = null) 147 | { 148 | // If an ID is passed to the method, we will set the where clause to check 149 | // the ID to allow developers to simply and quickly remove a single row 150 | // from their database without manually specifying the where clauses. 151 | if (!is_null($id)) { 152 | $this->where('_id', '=', $id); 153 | } 154 | 155 | $options = $this->session(); 156 | $wheres = $this->compileWheres(); 157 | $result = $this->collection->DeleteMany($wheres, $options); 158 | if (1 == (int) $result->isAcknowledged()) { 159 | return $result->getDeletedCount(); 160 | } 161 | 162 | return 0; 163 | } 164 | 165 | /** 166 | * @inheritdoc 167 | */ 168 | public function truncate(): bool 169 | { 170 | $result = $this->collection->drop(); 171 | 172 | return (1 == (int) $result->ok); 173 | } 174 | 175 | /** 176 | * Perform an update query. 177 | * 178 | * @param array $query 179 | * @param array $options 180 | * @return int 181 | */ 182 | protected function performUpdate($query, array $options = []) 183 | { 184 | // Update multiple items by default. 185 | if (!array_key_exists('multiple', $options)) { 186 | $options['multiple'] = true; 187 | } 188 | $options = $this->session($options); 189 | 190 | $wheres = $this->compileWheres(); 191 | $result = $this->collection->UpdateMany($wheres, $query, $options); 192 | if (1 == (int) $result->isAcknowledged()) { 193 | return $result->getModifiedCount() ? $result->getModifiedCount() : $result->getUpsertedCount(); 194 | } 195 | 196 | return 0; 197 | } 198 | 199 | protected function session(array $options = []) 200 | { 201 | if ($session = $this->connection->getSession()) { 202 | $options['session'] = $this->connection->getSession(); 203 | } 204 | 205 | return $options; 206 | } 207 | } 208 | --------------------------------------------------------------------------------