diff --git a/src/Eloquent/Builder.php b/src/Eloquent/Builder.php index 6ef960456..7ea18dfa9 100644 --- a/src/Eloquent/Builder.php +++ b/src/Eloquent/Builder.php @@ -6,11 +6,13 @@ use Illuminate\Database\ConnectionInterface; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; +use InvalidArgumentException; use MongoDB\Driver\Cursor; use MongoDB\Laravel\Collection; use MongoDB\Laravel\Helpers\QueriesRelationships; use MongoDB\Laravel\Internal\FindAndModifyCommandSubscriber; use MongoDB\Model\BSONDocument; +use MongoDB\Operation\FindOneAndUpdate; use function array_intersect_key; use function array_key_exists; @@ -195,7 +197,12 @@ public function raw($value = null) */ public function createOrFirst(array $attributes = [], array $values = []): Model { + if ($attributes === []) { + throw new InvalidArgumentException('You must provide attributes to check for duplicates'); + } + // Apply casting and default values to the attributes + // In case of duplicate key between the attributes and the values, the values have priority $instance = $this->newModelInstance($values + $attributes); $values = $instance->getAttributes(); $attributes = array_intersect_key($attributes, $values); @@ -207,8 +214,14 @@ public function createOrFirst(array $attributes = [], array $values = []): Model try { $document = $collection->findOneAndUpdate( $attributes, - ['$setOnInsert' => $values], - ['upsert' => true, 'new' => true, 'typeMap' => ['root' => 'array', 'document' => 'array']], + // Before MongoDB 5.0, $setOnInsert requires a non-empty document. + // This is should not be an issue as $values includes the query filter. + ['$setOnInsert' => (object) $values], + [ + 'upsert' => true, + 'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER, + 'typeMap' => ['root' => 'array', 'document' => 'array'], + ], ); } finally { $collection->getManager()->removeSubscriber($listener); diff --git a/tests/ModelTest.php b/tests/ModelTest.php index f4d459422..5ab6badee 100644 --- a/tests/ModelTest.php +++ b/tests/ModelTest.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Date; use Illuminate\Support\Str; +use InvalidArgumentException; use MongoDB\BSON\Binary; use MongoDB\BSON\ObjectID; use MongoDB\BSON\UTCDateTime; @@ -1047,40 +1048,47 @@ public function testNumericFieldName(): void public function testCreateOrFirst() { - $user1 = User::createOrFirst(['email' => 'taylorotwell@gmail.com']); + $user1 = User::createOrFirst(['email' => 'john.doe@example.com']); - $this->assertSame('taylorotwell@gmail.com', $user1->email); + $this->assertSame('john.doe@example.com', $user1->email); $this->assertNull($user1->name); $this->assertTrue($user1->wasRecentlyCreated); $user2 = User::createOrFirst( - ['email' => 'taylorotwell@gmail.com'], - ['name' => 'Taylor Otwell', 'birthday' => new DateTime('1987-05-28')], + ['email' => 'john.doe@example.com'], + ['name' => 'John Doe', 'birthday' => new DateTime('1987-05-28')], ); $this->assertEquals($user1->id, $user2->id); - $this->assertSame('taylorotwell@gmail.com', $user2->email); + $this->assertSame('john.doe@example.com', $user2->email); $this->assertNull($user2->name); $this->assertNull($user2->birthday); $this->assertFalse($user2->wasRecentlyCreated); $user3 = User::createOrFirst( - ['email' => 'abigailotwell@gmail.com'], - ['name' => 'Abigail Otwell', 'birthday' => new DateTime('1987-05-28')], + ['email' => 'jane.doe@example.com'], + ['name' => 'Jane Doe', 'birthday' => new DateTime('1987-05-28')], ); $this->assertNotEquals($user3->id, $user1->id); - $this->assertSame('abigailotwell@gmail.com', $user3->email); - $this->assertSame('Abigail Otwell', $user3->name); + $this->assertSame('jane.doe@example.com', $user3->email); + $this->assertSame('Jane Doe', $user3->name); $this->assertEquals(new DateTime('1987-05-28'), $user3->birthday); $this->assertTrue($user3->wasRecentlyCreated); $user4 = User::createOrFirst( - ['name' => 'Dries Vints'], - ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'], + ['name' => 'Robert Doe'], + ['name' => 'Maria Doe', 'email' => 'maria.doe@example.com'], ); - $this->assertSame('Nuno Maduro', $user4->name); + $this->assertSame('Maria Doe', $user4->name); $this->assertTrue($user4->wasRecentlyCreated); } + + public function testCreateOrFirstRequiresFilter() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('You must provide attributes to check for duplicates'); + User::createOrFirst([]); + } }