Skip to content

Commit b89006c

Browse files
authored
[11.x] Add IncrementOrCreate method to Eloquent (#54300)
* feat: added incrementOrCreate new method * added increment step and extra params to incrementOrCreate * StyleCI
1 parent b0667a6 commit b89006c

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

src/Illuminate/Database/Eloquent/Builder.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,25 @@ public function updateOrCreate(array $attributes, array $values = [])
622622
});
623623
}
624624

625+
/**
626+
* Create a record matching the attributes, or increment the existing record.
627+
*
628+
* @param array $attributes
629+
* @param string $column
630+
* @param int|float $default
631+
* @param int|float $step
632+
* @param array $extra
633+
* @return TModel
634+
*/
635+
public function incrementOrCreate(array $attributes, string $column = 'count', $default = 1, $step = 1, array $extra = [])
636+
{
637+
return tap($this->firstOrCreate($attributes, [$column => $default]), function ($instance) use ($column, $step, $extra) {
638+
if (! $instance->wasRecentlyCreated) {
639+
$instance->increment($column, $step, $extra);
640+
}
641+
});
642+
}
643+
625644
/**
626645
* Execute the query and get the first result or throw an exception.
627646
*

tests/Database/DatabaseEloquentBuilderCreateOrFirstTest.php

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,175 @@ public function testUpdateOrCreateMethodUpdatesRecordCreatedJustNow(): void
300300
], $result->toArray());
301301
}
302302

303+
public function testIncrementOrCreateMethodIncrementsExistingRecord(): void
304+
{
305+
$model = new EloquentBuilderCreateOrFirstTestModel();
306+
$this->mockConnectionForModel($model, 'SQLite');
307+
$model->getConnection()->shouldReceive('transactionLevel')->andReturn(0);
308+
$model->getConnection()->shouldReceive('getName')->andReturn('sqlite');
309+
310+
$model->getConnection()
311+
->expects('select')
312+
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], true)
313+
->andReturn([[
314+
'id' => 123,
315+
'attr' => 'foo',
316+
'count' => 1,
317+
'created_at' => '2023-01-01 00:00:00',
318+
'updated_at' => '2023-01-01 00:00:00',
319+
]]);
320+
321+
$model->getConnection()
322+
->expects('raw')
323+
->with('"count" + 1')
324+
->andReturn('2');
325+
326+
$model->getConnection()
327+
->expects('update')
328+
->with(
329+
'update "table" set "count" = ?, "updated_at" = ? where "id" = ?',
330+
['2', '2023-01-01 00:00:00', 123],
331+
)
332+
->andReturn(1);
333+
334+
$result = $model->newQuery()->incrementOrCreate(['attr' => 'foo'], 'count');
335+
$this->assertFalse($result->wasRecentlyCreated);
336+
$this->assertEquals([
337+
'id' => 123,
338+
'attr' => 'foo',
339+
'count' => 2,
340+
'created_at' => '2023-01-01T00:00:00.000000Z',
341+
'updated_at' => '2023-01-01T00:00:00.000000Z',
342+
], $result->toArray());
343+
}
344+
345+
public function testIncrementOrCreateMethodCreatesNewRecord(): void
346+
{
347+
$model = new EloquentBuilderCreateOrFirstTestModel();
348+
$this->mockConnectionForModel($model, 'SQLite', [123]);
349+
$model->getConnection()->shouldReceive('transactionLevel')->andReturn(0);
350+
$model->getConnection()->shouldReceive('getName')->andReturn('sqlite');
351+
352+
$model->getConnection()
353+
->expects('select')
354+
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], true)
355+
->andReturn([]);
356+
357+
$model->getConnection()->expects('insert')->with(
358+
'insert into "table" ("attr", "count", "updated_at", "created_at") values (?, ?, ?, ?)',
359+
['foo', '1', '2023-01-01 00:00:00', '2023-01-01 00:00:00'],
360+
)->andReturnTrue();
361+
362+
$result = $model->newQuery()->incrementOrCreate(['attr' => 'foo']);
363+
$this->assertTrue($result->wasRecentlyCreated);
364+
$this->assertEquals([
365+
'id' => 123,
366+
'attr' => 'foo',
367+
'count' => 1,
368+
'created_at' => '2023-01-01T00:00:00.000000Z',
369+
'updated_at' => '2023-01-01T00:00:00.000000Z',
370+
], $result->toArray());
371+
}
372+
373+
public function testIncrementOrCreateMethodIncrementParametersArePassed(): void
374+
{
375+
$model = new EloquentBuilderCreateOrFirstTestModel();
376+
$this->mockConnectionForModel($model, 'SQLite');
377+
$model->getConnection()->shouldReceive('transactionLevel')->andReturn(0);
378+
$model->getConnection()->shouldReceive('getName')->andReturn('sqlite');
379+
380+
$model->getConnection()
381+
->expects('select')
382+
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], true)
383+
->andReturn([[
384+
'id' => 123,
385+
'attr' => 'foo',
386+
'val' => 'bar',
387+
'count' => 1,
388+
'created_at' => '2023-01-01 00:00:00',
389+
'updated_at' => '2023-01-01 00:00:00',
390+
]]);
391+
392+
$model->getConnection()
393+
->expects('raw')
394+
->with('"count" + 2')
395+
->andReturn('3');
396+
397+
$model->getConnection()
398+
->expects('update')
399+
->with(
400+
'update "table" set "count" = ?, "val" = ?, "updated_at" = ? where "id" = ?',
401+
['3', 'baz', '2023-01-01 00:00:00', 123],
402+
)
403+
->andReturn(1);
404+
405+
$result = $model->newQuery()->incrementOrCreate(['attr' => 'foo'], step: 2, extra: ['val' => 'baz']);
406+
$this->assertFalse($result->wasRecentlyCreated);
407+
$this->assertEquals([
408+
'id' => 123,
409+
'attr' => 'foo',
410+
'count' => 3,
411+
'val' => 'baz',
412+
'created_at' => '2023-01-01T00:00:00.000000Z',
413+
'updated_at' => '2023-01-01T00:00:00.000000Z',
414+
], $result->toArray());
415+
}
416+
417+
public function testIncrementOrCreateMethodRetrievesRecordCreatedJustNow(): void
418+
{
419+
$model = new EloquentBuilderCreateOrFirstTestModel();
420+
$this->mockConnectionForModel($model, 'SQLite');
421+
$model->getConnection()->shouldReceive('transactionLevel')->andReturn(0);
422+
$model->getConnection()->shouldReceive('getName')->andReturn('sqlite');
423+
424+
$model->getConnection()
425+
->expects('select')
426+
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], true)
427+
->andReturn([]);
428+
429+
$sql = 'insert into "table" ("attr", "count", "updated_at", "created_at") values (?, ?, ?, ?)';
430+
$bindings = ['foo', '1', '2023-01-01 00:00:00', '2023-01-01 00:00:00'];
431+
432+
$model->getConnection()
433+
->expects('insert')
434+
->with($sql, $bindings)
435+
->andThrow(new UniqueConstraintViolationException('sqlite', $sql, $bindings, new Exception()));
436+
437+
$model->getConnection()
438+
->expects('select')
439+
->with('select * from "table" where ("attr" = ?) limit 1', ['foo'], false)
440+
->andReturn([[
441+
'id' => 123,
442+
'attr' => 'foo',
443+
'count' => 1,
444+
'created_at' => '2023-01-01 00:00:00',
445+
'updated_at' => '2023-01-01 00:00:00',
446+
]]);
447+
448+
$model->getConnection()
449+
->expects('raw')
450+
->with('"count" + 1')
451+
->andReturn('2');
452+
453+
$model->getConnection()
454+
->expects('update')
455+
->with(
456+
'update "table" set "count" = ?, "updated_at" = ? where "id" = ?',
457+
['2', '2023-01-01 00:00:00', 123],
458+
)
459+
->andReturn(1);
460+
461+
$result = $model->newQuery()->incrementOrCreate(['attr' => 'foo']);
462+
$this->assertFalse($result->wasRecentlyCreated);
463+
$this->assertEquals([
464+
'id' => 123,
465+
'attr' => 'foo',
466+
'count' => 2,
467+
'created_at' => '2023-01-01T00:00:00.000000Z',
468+
'updated_at' => '2023-01-01T00:00:00.000000Z',
469+
], $result->toArray());
470+
}
471+
303472
protected function mockConnectionForModel(Model $model, string $database, array $lastInsertIds = []): void
304473
{
305474
$grammarClass = 'Illuminate\Database\Query\Grammars\\'.$database.'Grammar';

0 commit comments

Comments
 (0)