Skip to content

BelongsToMany#updateOrCreate is checking the wrong table for Model existence #34083

Closed
@Harti

Description

@Harti
  • Laravel Version: 7.25.0
  • PHP Version: 7.4.7
  • Database Driver & Version: MySQL 8.0.20

Description:

For Illuminate\Database\Eloquent\Relations\BelongsToMany relations only, the updateOrCreate() method is checking the pivot table instead of the related table for Model existence:

Illuminate\Database\Eloquent\Relations\BelongsToMany
    public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true)
    {
        if (is_null($instance = $this->where($attributes)->first())) {
            return $this->create($values, $joining, $touch);
        }

        $instance->fill($values);

        $instance->save(['touch' => false]);

        return $instance;
    }

It appears this was an oversight because the BelongsToMany#create method however would then proceed to create an entity in $this->related.

As a side note, the HasOneOrMany#updateOrCreate method is vastly different with its tap($this->firstOrNew(...), ...) call.

Proposed fix (change first line in method body):
    public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true)
    {
        if (is_null($instance = $this->related->where($attributes)->first())) {
            return $this->create($values, $joining, $touch);
        }

        $instance->fill($values);

        $instance->save(['touch' => false]);

        return $instance;
    }

Steps To Reproduce:

Example Gist with migrations, test and stacktrace (friend User-User relation): https://gist.github.com/Harti/2afb5afd524f3639045b3d57756655fd

Inline Example:

Role::create(['name' => 'existing.role', 'description' => 'foo']);
$user->roles()->updateOrCreate(['name' => 'existing.role'], ['description' => 'bar']);

parent::assertEquals(1, Role::count()); // assertion fails (or is never reached); count equals 2

A second/duplicate entry is inserted into the roles table (or a Integrity Constraint Violation is thrown, should name be unique).
Expected behavior: The existing Role is updated, and saved to the $user->roles() relation (which also only seems to happen if the instance was newly created).

Best regards,

Harti

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions