Skip to content

Commit 65418d2

Browse files
committed
createOrFirst in transaction fallback to firstOrCreate
1 parent 990612e commit 65418d2

File tree

2 files changed

+46
-6
lines changed

2 files changed

+46
-6
lines changed

Diff for: src/Eloquent/Builder.php

+28-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
namespace MongoDB\Laravel\Eloquent;
66

7-
use Illuminate\Database\ConnectionInterface;
87
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
98
use MongoDB\Driver\Cursor;
109
use MongoDB\Driver\Exception\WriteException;
10+
use MongoDB\Laravel\Connection;
1111
use MongoDB\Laravel\Helpers\QueriesRelationships;
1212
use MongoDB\Laravel\Query\AggregationBuilder;
1313
use MongoDB\Model\BSONDocument;
@@ -198,8 +198,34 @@ public function raw($value = null)
198198
return $results;
199199
}
200200

201+
/**
202+
* Get the first record matching the attributes. If the record is not found, create it.
203+
*
204+
* @param array $attributes
205+
* @param array $values
206+
*/
207+
public function firstOrCreate(array $attributes = [], array $values = []): Model|Builder
208+
{
209+
$instance = (clone $this)->where($attributes)->first();
210+
if ($instance !== null) {
211+
return $instance;
212+
}
213+
214+
// createOrFirst is not supported in transaction.
215+
if ($this->getConnection()->getSession()?->isInTransaction()) {
216+
return $this->create(array_merge($attributes, $values));
217+
}
218+
219+
return $this->createOrFirst($attributes, $values);
220+
}
221+
201222
public function createOrFirst(array $attributes = [], array $values = []): Builder|Model
202223
{
224+
// The duplicate key error would abort the transaction. Using the regular firstOrCreate in that case.
225+
if ($this->getConnection()->getSession()?->isInTransaction()) {
226+
return $this->firstOrCreate($attributes, $values);
227+
}
228+
203229
try {
204230
return $this->create(array_merge($attributes, $values));
205231
} catch (WriteException $e) {
@@ -234,8 +260,7 @@ protected function addUpdatedAtColumn(array $values)
234260
return $values;
235261
}
236262

237-
/** @return ConnectionInterface */
238-
public function getConnection()
263+
public function getConnection(): Connection
239264
{
240265
return $this->query->getConnection();
241266
}

Diff for: tests/ModelTest.php

+18-3
Original file line numberDiff line numberDiff line change
@@ -1048,12 +1048,19 @@ public function testNumericFieldName(): void
10481048
$this->assertEquals([3 => 'two.three'], $found[2]);
10491049
}
10501050

1051-
public function testCreateOrFirst()
1051+
#[TestWith([true])]
1052+
#[TestWith([false])]
1053+
public function testCreateOrFirst(bool $transaction)
10521054
{
1053-
DB::connection('mongodb')
1055+
$connection = DB::connection('mongodb');
1056+
$connection
10541057
->getCollection('users')
10551058
->createIndex(['email' => 1], ['unique' => true]);
10561059

1060+
if ($transaction) {
1061+
$connection->beginTransaction();
1062+
}
1063+
10571064
Carbon::setTestNow('2010-06-22');
10581065
$createdAt = Carbon::now()->getTimestamp();
10591066
$events = [];
@@ -1081,7 +1088,11 @@ public function testCreateOrFirst()
10811088
$this->assertFalse($user2->wasRecentlyCreated);
10821089
$this->assertEquals($createdAt, $user1->created_at->getTimestamp());
10831090
$this->assertEquals($createdAt, $user1->updated_at->getTimestamp());
1084-
$this->assertEquals(['saving', 'creating'], $events);
1091+
if ($transaction) {
1092+
$this->assertEquals([], $events);
1093+
} else {
1094+
$this->assertEquals(['saving', 'creating'], $events);
1095+
}
10851096

10861097
$events = [];
10871098
$user3 = User::createOrFirst(
@@ -1107,6 +1118,10 @@ public function testCreateOrFirst()
11071118
$this->assertSame('Maria Doe', $user4->name);
11081119
$this->assertTrue($user4->wasRecentlyCreated);
11091120
$this->assertEquals(['saving', 'creating', 'created', 'saved'], $events);
1121+
1122+
if ($transaction) {
1123+
$connection->commit();
1124+
}
11101125
}
11111126

11121127
#[TestWith([['_id' => new ObjectID()]])]

0 commit comments

Comments
 (0)