Skip to content

Commit 29cf890

Browse files
alcaeusklinsonlevon80999
committed
Add support for transactions
Co-authored-by: klinson <klinson@163.com> Co-authored-by: levon80999 <levonb@ucraft.com>
1 parent 46aa0fc commit 29cf890

File tree

6 files changed

+674
-62
lines changed

6 files changed

+674
-62
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ This package adds functionalities to the Eloquent model and Query builder for Mo
3737
- [Query Builder](#query-builder)
3838
- [Basic Usage](#basic-usage-2)
3939
- [Available operations](#available-operations)
40+
- [Transactions](#transactions)
4041
- [Schema](#schema)
4142
- [Basic Usage](#basic-usage-3)
4243
- [Geospatial indexes](#geospatial-indexes)
@@ -968,6 +969,46 @@ If you are familiar with [Eloquent Queries](http://laravel.com/docs/queries), th
968969
### Available operations
969970
To see the available operations, check the [Eloquent](#eloquent) section.
970971

972+
Transactions
973+
-------
974+
Transactions require MongoDB version ^4.0 as well as deployment of replica set or sharded clusters. You can find more information [in the MongoDB docs](https://docs.mongodb.com/manual/core/transactions/)
975+
976+
### Basic Usage
977+
978+
Transaction supports all operations.
979+
980+
```php
981+
DB::transaction(function () {
982+
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => 'klinsonup@gmail.com']);
983+
DB::collection('users')->where('name', 'john')->update(['age' => 20]);
984+
DB::collection('users')->where('name', 'john')->delete();
985+
});
986+
```
987+
988+
```php
989+
// begin a transaction
990+
DB::beginTransaction();
991+
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => 'klinsonup@gmail.com']);
992+
DB::collection('users')->where('name', 'john')->update(['age' => 20]);
993+
DB::collection('users')->where('name', 'john')->delete();
994+
995+
// you can commit your changes
996+
DB::commit();
997+
998+
// you can also rollback them
999+
//DB::rollBack();
1000+
```
1001+
**NOTE:** Transactions in MongoDB cannot be nested. DB::beginTransaction() function will start new transactions in a new created or existing session and will raise the RuntimeException when transactions already exist. See more in MongoDB official docs [Transactions and Sessions](https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-sessions)
1002+
```php
1003+
// This code will raise a RuntimeException
1004+
DB::beginTransaction();
1005+
User::create(['name' => 'john', 'age' => 20, 'title' => 'admin']);
1006+
DB::beginTransaction()
1007+
DB::collection('users')->where('name', 'john')->update(['age' => 20]);
1008+
DB::commit()
1009+
DB::rollBack();
1010+
```
1011+
9711012
Schema
9721013
------
9731014
The database driver also has (limited) schema builder support. You can easily manipulate collections and set indexes.

phpunit.xml.dist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
<file>tests/QueryBuilderTest.php</file>
2020
<file>tests/QueryTest.php</file>
2121
</testsuite>
22+
<testsuite name="transaction">
23+
<file>tests/TransactionTest.php</file>
24+
</testsuite>
2225
<testsuite name="model">
2326
<file>tests/ModelTest.php</file>
2427
<file>tests/RelationsTest.php</file>

src/Concerns/ManagesTransactions.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
namespace Jenssegers\Mongodb\Concerns;
4+
5+
use Closure;
6+
use MongoDB\Client;
7+
use MongoDB\Driver\Exception\RuntimeException;
8+
use MongoDB\Driver\Session;
9+
use function MongoDB\with_transaction;
10+
11+
trait ManagesTransactions
12+
{
13+
protected ?Session $session = null;
14+
15+
/**
16+
* @return Client
17+
*/
18+
abstract public function getMongoClient();
19+
20+
public function getSession(): ?Session
21+
{
22+
return $this->session;
23+
}
24+
25+
private function getSessionOrCreate(): Session
26+
{
27+
if ($this->session === null) {
28+
$this->session = $this->getMongoClient()->startSession();
29+
}
30+
31+
return $this->session;
32+
}
33+
34+
private function getSessionOrThrow(): Session
35+
{
36+
$session = $this->getSession();
37+
38+
if ($session === null) {
39+
throw new RuntimeException('There is no active session.');
40+
}
41+
42+
return $session;
43+
}
44+
45+
/**
46+
* Use the existing or create new session and start a transaction in session.
47+
*
48+
* In version 4.0, MongoDB supports multi-document transactions on replica sets.
49+
* In version 4.2, MongoDB introduces distributed transactions, which adds support for multi-document transactions on sharded clusters and incorporates the existing support for multi-document transactions on replica sets.
50+
*
51+
* @see https://docs.mongodb.com/manual/core/transactions/
52+
*/
53+
public function beginTransaction(array $options = []): void
54+
{
55+
$this->getSessionOrCreate()->startTransaction($options);
56+
$this->transactions = 1;
57+
}
58+
59+
/**
60+
* Commit transaction in this session.
61+
*/
62+
public function commit(): void
63+
{
64+
$this->getSessionOrThrow()->commitTransaction();
65+
$this->transactions = 0;
66+
}
67+
68+
/**
69+
* Rollback transaction in this session.
70+
*/
71+
public function rollBack($toLevel = null): void
72+
{
73+
$this->getSessionOrThrow()->abortTransaction();
74+
$this->transactions = 0;
75+
}
76+
77+
/**
78+
* Static transaction function realize the with_transaction functionality provided by MongoDB.
79+
*
80+
* @param int $attempts
81+
*/
82+
public function transaction(Closure $callback, $attempts = 1, array $options = []): mixed
83+
{
84+
$attemptsLeft = $attempts;
85+
$callbackResult = null;
86+
87+
$session = $this->getSessionOrCreate();
88+
89+
$callbackFunction = function (Session $session) use ($callback, &$attemptsLeft, &$callbackResult) {
90+
$attemptsLeft--;
91+
92+
if ($attemptsLeft < 0) {
93+
$session->abortTransaction();
94+
95+
return;
96+
}
97+
98+
$callbackResult = $callback();
99+
};
100+
101+
with_transaction($session, $callbackFunction, $options);
102+
103+
return $callbackResult;
104+
}
105+
}

src/Connection.php

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
use Illuminate\Database\Connection as BaseConnection;
66
use Illuminate\Support\Arr;
77
use InvalidArgumentException;
8+
use Jenssegers\Mongodb\Concerns\ManagesTransactions;
89
use MongoDB\Client;
910

1011
class Connection extends BaseConnection
1112
{
13+
use ManagesTransactions;
14+
1215
/**
1316
* The MongoDB database handler.
1417
*
@@ -26,7 +29,7 @@ class Connection extends BaseConnection
2629
/**
2730
* Create a new database connection instance.
2831
*
29-
* @param array $config
32+
* @param array $config
3033
*/
3134
public function __construct(array $config)
3235
{
@@ -57,7 +60,7 @@ public function __construct(array $config)
5760
/**
5861
* Begin a fluent query against a database collection.
5962
*
60-
* @param string $collection
63+
* @param string $collection
6164
* @return Query\Builder
6265
*/
6366
public function collection($collection)
@@ -70,8 +73,8 @@ public function collection($collection)
7073
/**
7174
* Begin a fluent query against a database collection.
7275
*
73-
* @param string $table
74-
* @param string|null $as
76+
* @param string $table
77+
* @param string|null $as
7578
* @return Query\Builder
7679
*/
7780
public function table($table, $as = null)
@@ -82,7 +85,7 @@ public function table($table, $as = null)
8285
/**
8386
* Get a MongoDB collection.
8487
*
85-
* @param string $name
88+
* @param string $name
8689
* @return Collection
8790
*/
8891
public function getCollection($name)
@@ -129,9 +132,10 @@ public function getDatabaseName()
129132
/**
130133
* Get the name of the default database based on db config or try to detect it from dsn.
131134
*
132-
* @param string $dsn
133-
* @param array $config
135+
* @param string $dsn
136+
* @param array $config
134137
* @return string
138+
*
135139
* @throws InvalidArgumentException
136140
*/
137141
protected function getDefaultDatabaseName($dsn, $config)
@@ -150,9 +154,9 @@ protected function getDefaultDatabaseName($dsn, $config)
150154
/**
151155
* Create a new MongoDB connection.
152156
*
153-
* @param string $dsn
154-
* @param array $config
155-
* @param array $options
157+
* @param string $dsn
158+
* @param array $config
159+
* @param array $options
156160
* @return \MongoDB\Client
157161
*/
158162
protected function createConnection($dsn, array $config, array $options)
@@ -186,7 +190,7 @@ public function disconnect()
186190
/**
187191
* Determine if the given configuration array has a dsn string.
188192
*
189-
* @param array $config
193+
* @param array $config
190194
* @return bool
191195
*/
192196
protected function hasDsnString(array $config)
@@ -197,7 +201,7 @@ protected function hasDsnString(array $config)
197201
/**
198202
* Get the DSN string form configuration.
199203
*
200-
* @param array $config
204+
* @param array $config
201205
* @return string
202206
*/
203207
protected function getDsnString(array $config)
@@ -208,7 +212,7 @@ protected function getDsnString(array $config)
208212
/**
209213
* Get the DSN string for a host / port configuration.
210214
*
211-
* @param array $config
215+
* @param array $config
212216
* @return string
213217
*/
214218
protected function getHostDsn(array $config)
@@ -232,7 +236,7 @@ protected function getHostDsn(array $config)
232236
/**
233237
* Create a DSN string from a configuration.
234238
*
235-
* @param array $config
239+
* @param array $config
236240
* @return string
237241
*/
238242
protected function getDsn(array $config)
@@ -285,7 +289,7 @@ protected function getDefaultSchemaGrammar()
285289
/**
286290
* Set database.
287291
*
288-
* @param \MongoDB\Database $db
292+
* @param \MongoDB\Database $db
289293
*/
290294
public function setDatabase(\MongoDB\Database $db)
291295
{
@@ -295,8 +299,8 @@ public function setDatabase(\MongoDB\Database $db)
295299
/**
296300
* Dynamically pass methods to the connection.
297301
*
298-
* @param string $method
299-
* @param array $parameters
302+
* @param string $method
303+
* @param array $parameters
300304
* @return mixed
301305
*/
302306
public function __call($method, $parameters)

0 commit comments

Comments
 (0)