Skip to content

Commit 0e2ab47

Browse files
Merge 4.1 into 4.2 (#2854)
2 parents 3c3b2bd + 79df465 commit 0e2ab47

File tree

5 files changed

+281
-45
lines changed

5 files changed

+281
-45
lines changed

docs/feature-compatibility.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ The following Eloquent methods are not supported in {+odm-short+}:
136136
- *Unsupported*
137137

138138
* - Grouping
139-
- Partially supported, use :ref:`Aggregation Builders <laravel-query-builder-aggregates>`.
139+
- Partially supported, use :ref:`Aggregations <laravel-query-builder-aggregations>`.
140140

141141
* - Limit and Offset
142142
- ✓
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Models;
6+
7+
use MongoDB\Laravel\Eloquent\Model;
8+
9+
class Account extends Model
10+
{
11+
protected $connection = 'mongodb';
12+
protected $fillable = ['number', 'balance'];
13+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Controllers;
6+
7+
use App\Models\Account;
8+
use Illuminate\Support\Facades\DB;
9+
use MongoDB\Laravel\Tests\TestCase;
10+
11+
class TransactionsTest extends TestCase
12+
{
13+
/**
14+
* @runInSeparateProcess
15+
* @preserveGlobalState disabled
16+
*/
17+
public function testTransactionCallback(): void
18+
{
19+
require_once __DIR__ . '/Account.php';
20+
21+
Account::truncate();
22+
23+
Account::insert([
24+
[
25+
'number' => 223344,
26+
'balance' => 5000,
27+
],
28+
[
29+
'number' => 776655,
30+
'balance' => 100,
31+
],
32+
]);
33+
34+
// begin transaction callback
35+
DB::transaction(function () {
36+
$transferAmount = 200;
37+
38+
$sender = Account::where('number', 223344)->first();
39+
$sender->balance -= $transferAmount;
40+
$sender->save();
41+
42+
$receiver = Account::where('number', 776655)->first();
43+
$receiver->balance += $transferAmount;
44+
$receiver->save();
45+
});
46+
// end transaction callback
47+
48+
$sender = Account::where('number', 223344)->first();
49+
$receiver = Account::where('number', 776655)->first();
50+
51+
$this->assertEquals(4800, $sender->balance);
52+
$this->assertEquals(300, $receiver->balance);
53+
}
54+
55+
public function testTransactionCommit(): void
56+
{
57+
require_once __DIR__ . '/Account.php';
58+
59+
Account::truncate();
60+
61+
Account::insert([
62+
[
63+
'number' => 223344,
64+
'balance' => 5000,
65+
],
66+
[
67+
'number' => 776655,
68+
'balance' => 100,
69+
],
70+
]);
71+
72+
// begin commit transaction
73+
DB::beginTransaction();
74+
$oldAccount = Account::where('number', 223344)->first();
75+
76+
$newAccount = Account::where('number', 776655)->first();
77+
$newAccount->balance += $oldAccount->balance;
78+
$newAccount->save();
79+
80+
$oldAccount->delete();
81+
DB::commit();
82+
// end commit transaction
83+
84+
$acct1 = Account::where('number', 223344)->first();
85+
$acct2 = Account::where('number', 776655)->first();
86+
87+
$this->assertNull($acct1);
88+
$this->assertEquals(5100, $acct2->balance);
89+
}
90+
91+
public function testTransactionRollback(): void
92+
{
93+
require_once __DIR__ . '/Account.php';
94+
95+
Account::truncate();
96+
Account::insert([
97+
[
98+
'number' => 223344,
99+
'balance' => 200,
100+
],
101+
[
102+
'number' => 776655,
103+
'balance' => 0,
104+
],
105+
[
106+
'number' => 990011,
107+
'balance' => 0,
108+
],
109+
]);
110+
111+
// begin rollback transaction
112+
DB::beginTransaction();
113+
114+
$sender = Account::where('number', 223344)->first();
115+
$receiverA = Account::where('number', 776655)->first();
116+
$receiverB = Account::where('number', 990011)->first();
117+
118+
$amountA = 100;
119+
$amountB = 200;
120+
121+
$sender->balance -= $amountA;
122+
$receiverA->balance += $amountA;
123+
124+
$sender->balance -= $amountB;
125+
$receiverB->balance += $amountB;
126+
127+
if ($sender->balance < 0) {
128+
// insufficient balance, roll back the transaction
129+
DB::rollback();
130+
} else {
131+
DB::commit();
132+
}
133+
134+
// end rollback transaction
135+
136+
$sender = Account::where('number', 223344)->first();
137+
$receiverA = Account::where('number', 776655)->first();
138+
$receiverB = Account::where('number', 990011)->first();
139+
140+
$this->assertEquals(200, $sender->balance);
141+
$this->assertEquals(0, $receiverA->balance);
142+
$this->assertEquals(0, $receiverB->balance);
143+
}
144+
}

docs/query-builder.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,7 @@ specified query:
878878
:end-before: end query elemMatch
879879

880880
To learn more about regular expression queries in MongoDB, see
881-
the :manual:`$elemMatch operator <reference/operator/query/elemMatch/>`
881+
the :manual:`$elemMatch operator </reference/operator/query/elemMatch/>`
882882
in the {+server-docs-name+}.
883883

884884
.. _laravel-query-builder-cursor-timeout:

docs/transactions.txt

Lines changed: 122 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,71 +9,150 @@ Transactions
99
:values: tutorial
1010

1111
.. meta::
12-
:keywords: php framework, odm, code example
12+
:keywords: php framework, odm, rollback, commit, callback, code example, acid, atomic, consistent, isolated, durable
1313

14-
MongoDB transactions require the following software and topology:
14+
.. contents:: On this page
15+
:local:
16+
:backlinks: none
17+
:depth: 2
18+
:class: singlecol
19+
20+
Overview
21+
--------
22+
23+
In this guide, you can learn how to perform a **transaction** in MongoDB by
24+
using the {+odm-long+}. Transactions let you run a sequence of write operations
25+
that update the data only after the transaction is committed.
26+
27+
If the transaction fails, the PHP library that manages MongoDB operations
28+
for {+odm-short+} ensures that MongoDB discards all the changes made within
29+
the transaction before they become visible. This property of transactions
30+
that ensures that all changes within a transaction are either applied or
31+
discarded is called **atomicity**.
32+
33+
MongoDB performs write operations on single documents atomically. If you
34+
need atomicity in write operations on multiple documents or data consistency
35+
across multiple documents for your operations, run them in a multi-document
36+
transaction.
37+
38+
Multi-document transactions are **ACID compliant** because MongoDB
39+
guarantees that the data in your transaction operations remains consistent,
40+
even if the driver encounters unexpected errors.
41+
42+
Learn how to perform transactions in the following sections of this guide:
43+
44+
- :ref:`laravel-transaction-requirements`
45+
- :ref:`laravel-transaction-callback`
46+
- :ref:`laravel-transaction-commit`
47+
- :ref:`laravel-transaction-rollback`
48+
49+
.. tip::
50+
51+
To learn more about transactions in MongoDB, see :manual:`Transactions </core/transactions/>`
52+
in the {+server-docs-name+}.
53+
54+
.. _laravel-transaction-requirements:
55+
56+
Requirements and Limitations
57+
----------------------------
58+
59+
To perform transactions in MongoDB, you must use the following MongoDB
60+
version and topology:
1561

1662
- MongoDB version 4.0 or later
1763
- A replica set deployment or sharded cluster
1864

19-
You can find more information :manual:`in the MongoDB docs </core/transactions/>`
65+
MongoDB server and {+odm-short+} have the following limitations:
2066

21-
.. code-block:: php
67+
- In MongoDB versions 4.2 and earlier, write operations performed within a
68+
transaction must be on existing collections. In MongoDB versions 4.4 and
69+
later, the server automatically creates collections as necessary when
70+
you perform write operations in a transaction. To learn more about this
71+
limitation, see :manual:`Create Collections and Indexes in a Transaction </core/transactions/#create-collections-and-indexes-in-a-transaction>`
72+
in the {+server-docs-name+}.
73+
- MongoDB does not support nested transactions. If you attempt to start a
74+
transaction within another one, the extension raises a ``RuntimeException``.
75+
To learn more about this limitation, see :manual:`Transactions and Sessions </core/transactions/#transactions-and-sessions>`
76+
in the {+server-docs-name+}.
77+
- The {+odm-long+} does not support the database testing traits
78+
``Illuminate\Foundation\Testing\DatabaseTransactions`` and ``Illuminate\Foundation\Testing\RefreshDatabase``.
79+
As a workaround, you can create migrations with the ``Illuminate\Foundation\Testing\DatabaseMigrations``
80+
trait to reset the database after each test.
2281

23-
DB::transaction(function () {
24-
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => '[email protected]']);
25-
DB::collection('users')->where('name', 'john')->update(['age' => 20]);
26-
DB::collection('users')->where('name', 'john')->delete();
27-
});
82+
.. _laravel-transaction-callback:
2883

29-
.. code-block:: php
84+
Run a Transaction in a Callback
85+
-------------------------------
86+
87+
This section shows how you can run a transaction in a callback.
3088

31-
// begin a transaction
32-
DB::beginTransaction();
33-
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => '[email protected]']);
34-
DB::collection('users')->where('name', 'john')->update(['age' => 20]);
35-
DB::collection('users')->where('name', 'john')->delete();
89+
When using this method of running a transaction, all the code in the
90+
callback method runs as a single transaction.
3691

37-
// commit changes
38-
DB::commit();
92+
In the following example, the transaction consists of write operations that
93+
transfer the funds from a bank account, represented by the ``Account`` model,
94+
to another account:
3995

40-
To abort a transaction, call the ``rollBack`` method at any point during the transaction:
96+
.. literalinclude:: /includes/fundamentals/transactions/TransactionsTest.php
97+
:language: php
98+
:dedent:
99+
:start-after: begin transaction callback
100+
:end-before: end transaction callback
101+
102+
You can optionally pass the maximum number of times to retry a failed transaction as the second parameter as shown in the following code example:
41103

42104
.. code-block:: php
105+
:emphasize-lines: 4
43106

44-
DB::beginTransaction();
45-
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => '[email protected]']);
107+
DB::transaction(function() {
108+
// transaction code
109+
},
110+
retries: 5,
111+
);
46112

47-
// Abort the transaction, discarding any data created as part of it
48-
DB::rollBack();
113+
.. _laravel-transaction-commit:
49114

115+
Begin and Commit a Transaction
116+
------------------------------
50117

51-
.. note::
118+
This section shows how to start and commit a transaction.
52119

53-
Transactions in MongoDB cannot be nested. DB::beginTransaction() function
54-
will start new transactions in a new created or existing session and will
55-
raise the RuntimeException when transactions already exist. See more in
56-
MongoDB official docs :manual:`Transactions and Sessions </core/transactions/#transactions-and-sessions>`.
120+
To use this method of starting and committing a transaction, call the
121+
``DB::beginTransaction()`` method to start the transaction. Then, call the
122+
``DB::commit()`` method to end the transaction, which applies all the updates
123+
performed within the transaction.
57124

58-
.. code-block:: php
125+
In the following example, the balance from the first account is moved to the
126+
second account, after which the first account is deleted:
59127

60-
DB::beginTransaction();
61-
User::create(['name' => 'john', 'age' => 20, 'title' => 'admin']);
128+
.. literalinclude:: /includes/fundamentals/transactions/TransactionsTest.php
129+
:language: php
130+
:dedent:
131+
:emphasize-lines: 1,9
132+
:start-after: begin commit transaction
133+
:end-before: end commit transaction
62134

63-
// This call to start a nested transaction will raise a RuntimeException
64-
DB::beginTransaction();
65-
DB::collection('users')->where('name', 'john')->update(['age' => 20]);
66-
DB::commit();
67-
DB::rollBack();
135+
.. _laravel-transaction-rollback:
68136

69-
Database Testing
70-
----------------
137+
Roll Back a Transaction
138+
-----------------------
71139

72-
For testing, the traits ``Illuminate\Foundation\Testing\DatabaseTransactions``
73-
and ``Illuminate\Foundation\Testing\RefreshDatabase`` are not yet supported.
74-
Instead, create migrations and use the ``DatabaseMigrations`` trait to reset
75-
the database after each test:
140+
This section shows how to roll back a transaction. A rollback reverts all the
141+
write operations performed within that transaction. This means that the
142+
data is reverted to its state before the transaction.
76143

77-
.. code-block:: php
144+
To perform the rollback, call the ``DB::rollback()`` function anytime before
145+
the transaction is committed.
146+
147+
In the following example, the transaction consists of write operations that
148+
transfer funds from one account, represented by the ``Account`` model, to
149+
multiple other accounts. If the sender account has insufficient funds, the
150+
transaction is rolled back, and none of the models are updated:
151+
152+
.. literalinclude:: /includes/fundamentals/transactions/TransactionsTest.php
153+
:language: php
154+
:dedent:
155+
:emphasize-lines: 1,18,20
156+
:start-after: begin rollback transaction
157+
:end-before: end rollback transaction
78158

79-
use Illuminate\Foundation\Testing\DatabaseMigrations;

0 commit comments

Comments
 (0)