Skip to content

Unexpected NULL in createOrFirst() with Multiple Unique Constraints #48235

@fuwasegu

Description

@fuwasegu

Laravel Version

10.21.0

PHP Version

8.2.8

Database Driver & Version

No response

Description

Summary

The current implementation of createOrFirst() in Laravel's Eloquent Builder has a behavior that could lead to unintended consequences when there are multiple unique constraints.

Current Behavior

The createOrFirst() method tries to create a record first and, if a UniqueConstraintViolationException occurs, it falls back to finding the record based on $attributes.

Here is the current implementation:

public function createOrFirst(array $attributes = [], array $values = [])
{
try {
return $this->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values)));
} catch (UniqueConstraintViolationException) {
return $this->useWritePdo()->where($attributes)->first();
}
}

Issue

Assume that we have two columns id_a and id_b, both of which are unique, and id_a is the primary key.

If the database already has the following records:

(id_a, id_b) = [(1, 100), (2, 200), (3, 300), (4, 400)]
And we call createOrFirst(['id_a' => 5], ['id_b' => 400])

The create() call in the try block will fail because of id_b being unique. However, the catch block only looks for a record where id_a = 5, which will return NULL.

Steps To Reproduce

  1. Create a database table with two unique columns, for example id_a and id_b. Make id_a the primary key.
CREATE TABLE example (
    id_a INT PRIMARY KEY,
    id_b UNIQUE
);
  1. Insert some initial records into the table.
INSERT INTO example (id_a, id_b) VALUES (1, 100), (2, 200), (3, 300), (4, 400);
  1. Use Laravel's Eloquent Builder to call the createOrFirst() method.
ExampleModel::createOrFirst(['id_a' => 5], ['id_b' => 400]);
  1. Observe that a UniqueConstraintViolationException is thrown for id_b.
  2. Check the returned value; it will be NULL because the catch block is only considering id_a for the search.

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