diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index f166b28bbbfe..c76e27324e9a 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -3915,6 +3915,26 @@ public function updateOrInsert(array $attributes, array|callable $values = []) return (bool) $this->limit(1)->update($values); } + /** + * Get the first record matching the attributes or insert it. + * + * @param array $attributes + * @param array $values + * @return object + */ + public function firstOrInsert(array $attributes, array $values = []): object + { + $record = $this->where($attributes)->first(); + + if ($record !== null) { + return $record; + } + + $this->insert(array_merge($attributes, $values)); + + return $this->where($attributes)->first(); + } + /** * Insert new records or update the existing ones. * diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 096b82450d25..bdd592e04197 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -4141,6 +4141,72 @@ public function testUpdateOrInsertMethodWorksWithEmptyUpdateValues() $builder->shouldNotHaveReceived('update'); } + public function testFirstOrInsertMethodRecordExists() + { + $builder = m::mock(Builder::class.'[where,first,insert]', [ + m::mock(ConnectionInterface::class), + new Grammar, + m::mock(Processor::class), + ]); + + $existingRecord = new stdClass(); + $existingRecord->name = 'Existing User'; + $existingRecord->email = 'foo@example.com'; + + $builder->shouldReceive('where') + ->once() + ->with(['email' => 'foo@example.com']) + ->andReturn(m::self()); + + $builder->shouldReceive('first') + ->once() + ->andReturn($existingRecord); + + $builder->shouldNotReceive('insert'); + + $result = $builder->firstOrInsert(['email' => 'foo@example.com'], ['name' => 'New Name']); + + $this->assertInstanceOf(stdClass::class, $result); + $this->assertEquals('Existing User', $result->name); + $this->assertEquals('foo@example.com', $result->email); + } + + public function testFirstOrInsertMethodRecordDoesNotExist() + { + $builder = m::mock(Builder::class.'[where,first,insert]', [ + m::mock(ConnectionInterface::class), + new Grammar, + m::mock(Processor::class), + ]); + + $insertedRecord = new stdClass(); + $insertedRecord->name = 'New User'; + $insertedRecord->email = 'bar@example.com'; + + $builder->shouldReceive('where') + ->twice() + ->with(['email' => 'bar@example.com']) + ->andReturn(m::self()); + + $builder->shouldReceive('first') + ->once() + ->andReturn(null); + + $builder->shouldReceive('insert') + ->once() + ->with(['email' => 'bar@example.com', 'name' => 'New User']) + ->andReturn(true); + + $builder->shouldReceive('first') + ->once()->andReturn($insertedRecord); + + $result = $builder->firstOrInsert(['email' => 'bar@example.com'], ['name' => 'New User']); + + $this->assertInstanceOf(stdClass::class, $result); + $this->assertEquals('New User', $result->name); + $this->assertEquals('bar@example.com', $result->email); + } + public function testDeleteMethod() { $builder = $this->getBuilder();