Skip to content

Commit 7e44ebb

Browse files
✨ Feat: Add firstOrInsert method to Query Builder with tests
This commit introduces a new `firstOrInsert` method to the Laravel Query Builder. This method efficiently retrieves the first record matching the given attributes, or inserts a new record with the provided attributes and values if no record is found. It includes comprehensive unit tests to ensure its functionality and reliability. This enhancement provides a convenient way to perform "first or insert" operations directly within the query builder, improving developer workflow and code conciseness.
1 parent 122e5c2 commit 7e44ebb

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

src/Illuminate/Database/Query/Builder.php

+20
Original file line numberDiff line numberDiff line change
@@ -3915,6 +3915,26 @@ public function updateOrInsert(array $attributes, array|callable $values = [])
39153915
return (bool) $this->limit(1)->update($values);
39163916
}
39173917

3918+
/**
3919+
* Get the first record matching the attributes or insert it.
3920+
*
3921+
* @param array $attributes
3922+
* @param array $values
3923+
* @return object
3924+
*/
3925+
public function firstOrInsert(array $attributes, array $values = []): object
3926+
{
3927+
$record = $this->where($attributes)->first();
3928+
3929+
if ($record !== null) {
3930+
return $record;
3931+
}
3932+
3933+
$this->insert(array_merge($attributes, $values));
3934+
3935+
return $this->where($attributes)->first();
3936+
}
3937+
39183938
/**
39193939
* Insert new records or update the existing ones.
39203940
*

tests/Database/DatabaseQueryBuilderTest.php

+66
Original file line numberDiff line numberDiff line change
@@ -4141,6 +4141,72 @@ public function testUpdateOrInsertMethodWorksWithEmptyUpdateValues()
41414141
$builder->shouldNotHaveReceived('update');
41424142
}
41434143

4144+
public function testFirstOrInsertMethodRecordExists()
4145+
{
4146+
$builder = m::mock(Builder::class.'[where,first,insert]', [
4147+
m::mock(ConnectionInterface::class),
4148+
new Grammar,
4149+
m::mock(Processor::class),
4150+
]);
4151+
4152+
$existingRecord = new stdClass();
4153+
$existingRecord->name = 'Existing User';
4154+
$existingRecord->email = '[email protected]';
4155+
4156+
$builder->shouldReceive('where')
4157+
->once()
4158+
->with(['email' => '[email protected]'])
4159+
->andReturn(m::self());
4160+
4161+
$builder->shouldReceive('first')
4162+
->once()
4163+
->andReturn($existingRecord);
4164+
4165+
$builder->shouldNotReceive('insert');
4166+
4167+
$result = $builder->firstOrInsert(['email' => '[email protected]'], ['name' => 'New Name']);
4168+
4169+
$this->assertInstanceOf(stdClass::class, $result);
4170+
$this->assertEquals('Existing User', $result->name);
4171+
$this->assertEquals('[email protected]', $result->email);
4172+
}
4173+
4174+
public function testFirstOrInsertMethodRecordDoesNotExist()
4175+
{
4176+
$builder = m::mock(Builder::class.'[where,first,insert]', [
4177+
m::mock(ConnectionInterface::class),
4178+
new Grammar,
4179+
m::mock(Processor::class),
4180+
]);
4181+
4182+
$insertedRecord = new stdClass();
4183+
$insertedRecord->name = 'New User';
4184+
$insertedRecord->email = '[email protected]';
4185+
4186+
$builder->shouldReceive('where')
4187+
->twice()
4188+
->with(['email' => '[email protected]'])
4189+
->andReturn(m::self());
4190+
4191+
$builder->shouldReceive('first')
4192+
->once()
4193+
->andReturn(null);
4194+
4195+
$builder->shouldReceive('insert')
4196+
->once()
4197+
->with(['email' => '[email protected]', 'name' => 'New User'])
4198+
->andReturn(true);
4199+
4200+
$builder->shouldReceive('first')
4201+
->once()->andReturn($insertedRecord);
4202+
4203+
$result = $builder->firstOrInsert(['email' => '[email protected]'], ['name' => 'New User']);
4204+
4205+
$this->assertInstanceOf(stdClass::class, $result);
4206+
$this->assertEquals('New User', $result->name);
4207+
$this->assertEquals('[email protected]', $result->email);
4208+
}
4209+
41444210
public function testDeleteMethod()
41454211
{
41464212
$builder = $this->getBuilder();

0 commit comments

Comments
 (0)