Skip to content

[10.x] Failing test/bug report in HasManyThrough updateOrCreate() since Laravel 10.21 #48529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
47 changes: 47 additions & 0 deletions tests/Integration/Database/EloquentHasManyThroughTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,53 @@ public function testHasSameParentAndThroughParentTable()

$this->assertEquals([1], $categories->pluck('id')->all());
}

public function testUpdateOrCreateWillNotUseIdFromParentModel()
{
// On Laravel 10.21.0, a bug was introduced that would update the wrong model when using `updateOrCreate()`,
// because the UPDATE statement would target a model based on the ID from the parent instead of the actual
// conditions that the `updateOrCreate()` targeted. This test replicates the case that causes this bug.

// Manually provide IDs, keep ID 1 and 2 free for the team-mates.
$user1 = User::create(['name' => Str::random(), 'id' => 3]);
$user2 = User::create(['name' => Str::random(), 'id' => 4]);

$team1 = Team::create(['owner_id' => $user1->id]);
$team2 = Team::create(['owner_id' => $user2->id]);

$teamMate1 = User::create(['name' => 'John', 'slug' => 'john-slug', 'team_id' => $team1->id, 'id' => 2]);
// $teamMate2->id should be the same as the $team1->id for the bug to occur.
$teamMate2 = User::create(['name' => 'Jane', 'slug' => 'jane-slug', 'team_id' => $team2->id, 'id' => $team1->id]);

$this->assertSame(2, $teamMate1->id);
$this->assertSame(1, $teamMate2->id);

$this->assertSame(2, $teamMate1->refresh()->id);
$this->assertSame(1, $teamMate2->refresh()->id);

$this->assertSame('john-slug', $teamMate1->slug);
$this->assertSame('jane-slug', $teamMate2->slug);

$this->assertSame('john-slug', $teamMate1->refresh()->slug);
$this->assertSame('jane-slug', $teamMate2->refresh()->slug);

$user1->teamMates()->updateOrCreate([
'name' => 'John',
// The `updateOrCreate()` method tries to retrieve an existing model first like `->where($conditions)->first()`.
// In our case, that will return the model with the name `John`. However, the ID of the model with the name
// `John` is hydrated to `1` – where the actual ID should be `2` for the model with the name `John` (see
// the assertions above). If the `->where($conditions)->first()` return a model, a `->fill()->save()`
// action is executed. Because the ID is incorrectly hydrated to `1`, it will now update the Jane
// model with *all* the attributes of the `John` model, instead of updating the `John` model.
], [
'slug' => 'john-doe',
]);

// Expect $teamMate1's slug to be updated to john-doe instead of john-old.
$this->assertSame('john-doe', $teamMate1->fresh()->slug);
// $teamMate2 should not be updated, because it belongs to a different user altogether.
$this->assertSame('jane-slug', $teamMate2->fresh()->slug);
}
}

class User extends Model
Expand Down