Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/Illuminate/Database/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,25 @@ public function updateOrCreate(array $attributes, array $values = [])
});
}

/**
* Create a record matching the attributes, or increment the existing record.
*
* @param array $attributes
* @param string $column
* @param int|float $default
* @param int|float $step
* @param array $extra
* @return TModel
*/
public function incrementOrCreate(array $attributes, string $column = 'count', $default = 1, $step = 1, array $extra = [])
{
return tap($this->firstOrCreate($attributes, [$column => $default]), function ($instance) use ($column, $step, $extra) {
if (! $instance->wasRecentlyCreated) {
$instance->increment($column, $step, $extra);
}
});
}

/**
* Execute the query and get the first result or throw an exception.
*
Expand Down
169 changes: 169 additions & 0 deletions tests/Database/DatabaseEloquentBuilderCreateOrFirstTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,175 @@ public function testUpdateOrCreateMethodUpdatesRecordCreatedJustNow(): void
], $result->toArray());
}

public function testIncrementOrCreateMethodIncrementsExistingRecord(): void
{
$model = new EloquentBuilderCreateOrFirstTestModel();
$this->mockConnectionForModel($model, 'SQLite');
$model->getConnection()->shouldReceive('transactionLevel')->andReturn(0);
$model->getConnection()->shouldReceive('getName')->andReturn('sqlite');

$model->getConnection()
->expects('select')
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], true)
->andReturn([[
'id' => 123,
'attr' => 'foo',
'count' => 1,
'created_at' => '2023-01-01 00:00:00',
'updated_at' => '2023-01-01 00:00:00',
]]);

$model->getConnection()
->expects('raw')
->with('"count" + 1')
->andReturn('2');

$model->getConnection()
->expects('update')
->with(
'update "table" set "count" = ?, "updated_at" = ? where "id" = ?',
['2', '2023-01-01 00:00:00', 123],
)
->andReturn(1);

$result = $model->newQuery()->incrementOrCreate(['attr' => 'foo'], 'count');
$this->assertFalse($result->wasRecentlyCreated);
$this->assertEquals([
'id' => 123,
'attr' => 'foo',
'count' => 2,
'created_at' => '2023-01-01T00:00:00.000000Z',
'updated_at' => '2023-01-01T00:00:00.000000Z',
], $result->toArray());
}

public function testIncrementOrCreateMethodCreatesNewRecord(): void
{
$model = new EloquentBuilderCreateOrFirstTestModel();
$this->mockConnectionForModel($model, 'SQLite', [123]);
$model->getConnection()->shouldReceive('transactionLevel')->andReturn(0);
$model->getConnection()->shouldReceive('getName')->andReturn('sqlite');

$model->getConnection()
->expects('select')
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], true)
->andReturn([]);

$model->getConnection()->expects('insert')->with(
'insert into "table" ("attr", "count", "updated_at", "created_at") values (?, ?, ?, ?)',
['foo', '1', '2023-01-01 00:00:00', '2023-01-01 00:00:00'],
)->andReturnTrue();

$result = $model->newQuery()->incrementOrCreate(['attr' => 'foo']);
$this->assertTrue($result->wasRecentlyCreated);
$this->assertEquals([
'id' => 123,
'attr' => 'foo',
'count' => 1,
'created_at' => '2023-01-01T00:00:00.000000Z',
'updated_at' => '2023-01-01T00:00:00.000000Z',
], $result->toArray());
}

public function testIncrementOrCreateMethodIncrementParametersArePassed(): void
{
$model = new EloquentBuilderCreateOrFirstTestModel();
$this->mockConnectionForModel($model, 'SQLite');
$model->getConnection()->shouldReceive('transactionLevel')->andReturn(0);
$model->getConnection()->shouldReceive('getName')->andReturn('sqlite');

$model->getConnection()
->expects('select')
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], true)
->andReturn([[
'id' => 123,
'attr' => 'foo',
'val' => 'bar',
'count' => 1,
'created_at' => '2023-01-01 00:00:00',
'updated_at' => '2023-01-01 00:00:00',
]]);

$model->getConnection()
->expects('raw')
->with('"count" + 2')
->andReturn('3');

$model->getConnection()
->expects('update')
->with(
'update "table" set "count" = ?, "val" = ?, "updated_at" = ? where "id" = ?',
['3', 'baz', '2023-01-01 00:00:00', 123],
)
->andReturn(1);

$result = $model->newQuery()->incrementOrCreate(['attr' => 'foo'], step: 2, extra: ['val' => 'baz']);
$this->assertFalse($result->wasRecentlyCreated);
$this->assertEquals([
'id' => 123,
'attr' => 'foo',
'count' => 3,
'val' => 'baz',
'created_at' => '2023-01-01T00:00:00.000000Z',
'updated_at' => '2023-01-01T00:00:00.000000Z',
], $result->toArray());
}

public function testIncrementOrCreateMethodRetrievesRecordCreatedJustNow(): void
{
$model = new EloquentBuilderCreateOrFirstTestModel();
$this->mockConnectionForModel($model, 'SQLite');
$model->getConnection()->shouldReceive('transactionLevel')->andReturn(0);
$model->getConnection()->shouldReceive('getName')->andReturn('sqlite');

$model->getConnection()
->expects('select')
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], true)
->andReturn([]);

$sql = 'insert into "table" ("attr", "count", "updated_at", "created_at") values (?, ?, ?, ?)';
$bindings = ['foo', '1', '2023-01-01 00:00:00', '2023-01-01 00:00:00'];

$model->getConnection()
->expects('insert')
->with($sql, $bindings)
->andThrow(new UniqueConstraintViolationException('sqlite', $sql, $bindings, new Exception()));

$model->getConnection()
->expects('select')
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], false)
->andReturn([[
'id' => 123,
'attr' => 'foo',
'count' => 1,
'created_at' => '2023-01-01 00:00:00',
'updated_at' => '2023-01-01 00:00:00',
]]);

$model->getConnection()
->expects('raw')
->with('"count" + 1')
->andReturn('2');

$model->getConnection()
->expects('update')
->with(
'update "table" set "count" = ?, "updated_at" = ? where "id" = ?',
['2', '2023-01-01 00:00:00', 123],
)
->andReturn(1);

$result = $model->newQuery()->incrementOrCreate(['attr' => 'foo']);
$this->assertFalse($result->wasRecentlyCreated);
$this->assertEquals([
'id' => 123,
'attr' => 'foo',
'count' => 2,
'created_at' => '2023-01-01T00:00:00.000000Z',
'updated_at' => '2023-01-01T00:00:00.000000Z',
], $result->toArray());
}

protected function mockConnectionForModel(Model $model, string $database, array $lastInsertIds = []): void
{
$grammarClass = 'Illuminate\Database\Query\Grammars\\'.$database.'Grammar';
Expand Down