Skip to content

BelongsToMany#updateOrCreate is not merging arguments before create #35798

Closed
@marcoboers

Description

@marcoboers
  • Laravel Version: 8.20.1
  • PHP Version: 7.4.8
  • Database Driver & Version: MariaDB 10.5.8

Description:

For Illuminate\Database\Eloquent\Relations\BelongsToMany relationships, the updateOrCreate method is not merging the $attributes and $values arrays.

This is different from the behaviour of the updateOrCreate methods on Builder, HasOneOrMany and HasManyThrough classes and from the documented behaviour "If no such flight exists, a new flight will be created which has the attributes resulting from merging the first argument array with the second argument array" eloquent#upserts.

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;
    }

Proposed solution
This solution is based on master (9.x) branch where the fix for #34083 is taken into account.

    public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true)
    {
        if (is_null($instance = $this->related->where($attributes)->first())) {
            $instance = $this->create($attributes, $joining, $touch);
        }

        $instance->fill($values);

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

        return $instance;
    }

Steps To Reproduce:

Schema::create('roles', function (Blueprint $table) {
    $table->increments('id');
    $table->unsignedBigInteger('created_by_id');
    $table->string('name'); // <-- required
    $table->timestamps();
});
$user = User::factory()->create();
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
$user = User::factory()->create();

// throws: Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1364 Field 'name' doesn't have a default value
$user->roles()->updateOrCreate(['name' => 'existing.role'], ['description' => 'bar']); 

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

When confirmed I'll submit the pull request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions