-
Notifications
You must be signed in to change notification settings - Fork 11.5k
Description
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:
framework/src/Illuminate/Database/Eloquent/Builder.php
Lines 580 to 587 in 96b15c7
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
- 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
);
- Insert some initial records into the table.
INSERT INTO example (id_a, id_b) VALUES (1, 100), (2, 200), (3, 300), (4, 400);
- Use Laravel's Eloquent Builder to call the createOrFirst() method.
ExampleModel::createOrFirst(['id_a' => 5], ['id_b' => 400]);
- Observe that a UniqueConstraintViolationException is thrown for id_b.
- Check the returned value; it will be NULL because the catch block is only considering id_a for the search.