Skip to content

Commit e1ae507

Browse files
authored
[10.x] Make the updateOrCreate methods in relations use firstOrCreate behind the scenes (#48213)
* Make the `updateOrCreate` methods in relations use `firstOrCreate` Related to #48160, #48192 * Fix tests * Fix: correctly apply unused `$joining` and `$touch` * Fix: `save()` should always accepts `['touch' => false]`
1 parent 8984139 commit e1ae507

File tree

5 files changed

+33
-27
lines changed

5 files changed

+33
-27
lines changed

src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -672,19 +672,13 @@ public function createOrFirst(array $attributes = [], array $values = [], array
672672
*/
673673
public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true)
674674
{
675-
if (is_null($instance = (clone $this)->where($attributes)->first())) {
676-
if (is_null($instance = $this->related->where($attributes)->first())) {
677-
return $this->create(array_merge($attributes, $values), $joining, $touch);
678-
} else {
679-
$this->attach($instance, $joining, $touch);
680-
}
681-
}
682-
683-
$instance->fill($values);
675+
return tap($this->firstOrCreate($attributes, $values, $joining, $touch), function ($instance) use ($values) {
676+
if (! $instance->wasRecentlyCreated) {
677+
$instance->fill($values);
684678

685-
$instance->save(['touch' => false]);
686-
687-
return $instance;
679+
$instance->save(['touch' => false]);
680+
}
681+
});
688682
}
689683

690684
/**

src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,11 @@ public function firstOrNew(array $attributes)
271271
*/
272272
public function updateOrCreate(array $attributes, array $values = [])
273273
{
274-
$instance = $this->firstOrNew($attributes);
275-
276-
$instance->fill($values)->save();
277-
278-
return $instance;
274+
return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) {
275+
if (! $instance->wasRecentlyCreated) {
276+
$instance->fill($values)->save();
277+
}
278+
});
279279
}
280280

281281
/**

src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,10 @@ public function createOrFirst(array $attributes = [], array $values = [])
267267
*/
268268
public function updateOrCreate(array $attributes, array $values = [])
269269
{
270-
return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
271-
$instance->fill($values);
272-
273-
$instance->save();
270+
return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) {
271+
if (! $instance->wasRecentlyCreated) {
272+
$instance->fill($values)->save();
273+
}
274274
});
275275
}
276276

tests/Database/DatabaseEloquentHasManyTest.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,9 @@ public function testUpdateOrCreateMethodFindsFirstModelAndUpdates()
227227
$relation->getQuery()->shouldReceive('where')->once()->with(['foo'])->andReturn($relation->getQuery());
228228
$relation->getQuery()->shouldReceive('first')->once()->with()->andReturn($model = m::mock(stdClass::class));
229229
$relation->getRelated()->shouldReceive('newInstance')->never();
230-
$model->shouldReceive('fill')->once()->with(['bar']);
230+
231+
$model->wasRecentlyCreated = false;
232+
$model->shouldReceive('fill')->once()->with(['bar'])->andReturn($model);
231233
$model->shouldReceive('save')->once();
232234

233235
$this->assertInstanceOf(stdClass::class, $relation->updateOrCreate(['foo'], ['bar']));
@@ -236,11 +238,15 @@ public function testUpdateOrCreateMethodFindsFirstModelAndUpdates()
236238
public function testUpdateOrCreateMethodCreatesNewModelWithForeignKeySet()
237239
{
238240
$relation = $this->getRelation();
241+
$relation->getQuery()->shouldReceive('withSavepointIfNeeded')->once()->andReturnUsing(function ($scope) {
242+
return $scope();
243+
});
239244
$relation->getQuery()->shouldReceive('where')->once()->with(['foo'])->andReturn($relation->getQuery());
240245
$relation->getQuery()->shouldReceive('first')->once()->with()->andReturn(null);
241-
$relation->getRelated()->shouldReceive('newInstance')->once()->with(['foo'])->andReturn($model = m::mock(Model::class));
246+
$relation->getRelated()->shouldReceive('newInstance')->once()->with(['foo', 'bar'])->andReturn($model = m::mock(Model::class));
247+
248+
$model->wasRecentlyCreated = true;
242249
$model->shouldReceive('save')->once()->andReturn(true);
243-
$model->shouldReceive('fill')->once()->with(['bar']);
244250
$model->shouldReceive('setAttribute')->once()->with('foreign_key', 1);
245251

246252
$this->assertInstanceOf(Model::class, $relation->updateOrCreate(['foo'], ['bar']));

tests/Database/DatabaseEloquentMorphTest.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,10 @@ public function testUpdateOrCreateMethodFindsFirstModelAndUpdates()
302302
$relation->getQuery()->shouldReceive('where')->once()->with(['foo'])->andReturn($relation->getQuery());
303303
$relation->getQuery()->shouldReceive('first')->once()->with()->andReturn($model = m::mock(Model::class));
304304
$relation->getRelated()->shouldReceive('newInstance')->never();
305+
306+
$model->wasRecentlyCreated = false;
305307
$model->shouldReceive('setAttribute')->never();
306-
$model->shouldReceive('fill')->once()->with(['bar']);
308+
$model->shouldReceive('fill')->once()->with(['bar'])->andReturn($model);
307309
$model->shouldReceive('save')->once();
308310

309311
$this->assertInstanceOf(Model::class, $relation->updateOrCreate(['foo'], ['bar']));
@@ -312,13 +314,17 @@ public function testUpdateOrCreateMethodFindsFirstModelAndUpdates()
312314
public function testUpdateOrCreateMethodCreatesNewMorphModel()
313315
{
314316
$relation = $this->getOneRelation();
317+
$relation->getQuery()->shouldReceive('withSavepointIfNeeded')->once()->andReturnUsing(function ($scope) {
318+
return $scope();
319+
});
315320
$relation->getQuery()->shouldReceive('where')->once()->with(['foo'])->andReturn($relation->getQuery());
316321
$relation->getQuery()->shouldReceive('first')->once()->with()->andReturn(null);
317-
$relation->getRelated()->shouldReceive('newInstance')->once()->with(['foo'])->andReturn($model = m::mock(Model::class));
322+
$relation->getRelated()->shouldReceive('newInstance')->once()->with(['foo', 'bar'])->andReturn($model = m::mock(Model::class));
323+
324+
$model->wasRecentlyCreated = true;
318325
$model->shouldReceive('setAttribute')->once()->with('morph_id', 1);
319326
$model->shouldReceive('setAttribute')->once()->with('morph_type', get_class($relation->getParent()));
320327
$model->shouldReceive('save')->once()->andReturn(true);
321-
$model->shouldReceive('fill')->once()->with(['bar']);
322328

323329
$this->assertInstanceOf(Model::class, $relation->updateOrCreate(['foo'], ['bar']));
324330
}

0 commit comments

Comments
 (0)