Skip to content

Commit

Permalink
✨ Feat: Add firstOrInsert method to Query Builder with tests
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mohammadrasoulasghari committed Feb 13, 2025
1 parent 122e5c2 commit 03ec622
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/Illuminate/Database/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 (! is_null($record)) {
return $record;
}

$this->insert(array_merge($attributes, $values));

return $this->where($attributes)->first();
}

/**
* Insert new records or update the existing ones.
*
Expand Down
66 changes: 66 additions & 0 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '[email protected]';

$builder->shouldReceive('where')
->once()
->with(['email' => '[email protected]'])
->andReturn(m::self());

$builder->shouldReceive('first')
->once()
->andReturn($existingRecord);

$builder->shouldNotReceive('insert');

$result = $builder->firstOrInsert(['email' => '[email protected]'], ['name' => 'New Name']);

$this->assertInstanceOf(stdClass::class, $result);
$this->assertEquals('Existing User', $result->name);
$this->assertEquals('[email protected]', $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 = '[email protected]';

$builder->shouldReceive('where')
->twice()
->with(['email' => '[email protected]'])
->andReturn(m::self());

$builder->shouldReceive('first')
->once()
->andReturn(null);

$builder->shouldReceive('insert')
->once()
->with(['email' => '[email protected]', 'name' => 'New User'])
->andReturn(true);

$builder->shouldReceive('first')
->once()->andReturn($insertedRecord);

$result = $builder->firstOrInsert(['email' => '[email protected]'], ['name' => 'New User']);

$this->assertInstanceOf(stdClass::class, $result);
$this->assertEquals('New User', $result->name);
$this->assertEquals('[email protected]', $result->email);
}

public function testDeleteMethod()
{
$builder = $this->getBuilder();
Expand Down

0 comments on commit 03ec622

Please sign in to comment.