-
Notifications
You must be signed in to change notification settings - Fork 11.1k
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
[10.x] Adds a createOrFirst
method to Eloquent
#47973
Conversation
…ow constants on traits
One thing missing here is an extension hook for custom drivers to add their own way of detecting unique constraint violations. |
@antonkomarev @michaelnabil230 instead of downvoting, could you please tell why you don't like the PR? |
Don't wait for answers Particularly I believe that the PR is very well done and has a clear purpose given the author's explanation, I just don't agree with the name, I think it can be confusing mainly about the fact that we already have something similar, |
I agree with you on the same thing |
@driesvints I downvoted this PR because it will work as expected only in case when there is UNIQUE constraint in the table, but nobody will protect developer from using new method without such constraint. Then it will work exactly same way as default I'd prefer to have new exception type which will be thrown from |
I thought that too, but not sure if it's an issue. Sounds more like a documentation problem and the difference should be noted in the docs. Also, other frameworks have a similar API for these cases, like the Actually, after taking a look at the Rails implementation, I noticed that the
Right now, a race condition may cause unexpected exceptions with the current implementation of the
I actually thought about introducing a new |
Actually, the more I think about the |
… of QueryException
…ondition in the latter
…wly added UniqueConstraintViolationException
protected function matchesUniqueConstraintException(Exception $exception) | ||
{ | ||
// It's up to each child class of Connection to detect a unique constraint violation. | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about this default implementation. Please, let me know what y'all think about this. The only issue I see here is that custom drivers should implement this method, otherwise the createOrFirst
won't work.
Alright, y'all. I've made some changes and updated the PR description. In summary, I've added a |
Awesome! 🔥 |
Thanks 👍 |
* feat: 🎸 Support laravel/framework#47973 * ci: 🎡 Test on both ^10.20 and 10.19.* * ci: 🎡 migrate configuration * ci: 🎡 Workaround for sqlsrv laravel/framework#47937
@tonysm — just FYI, the MySQL error code is available in protected function isUniqueConstraintError(Exception $exception)
{
return $exception instanceof QueryException
&& is_array($exception->errorInfo)
&& $exception->errorInfo[1] == 1062;
} |
@crishoj Nice comment! I used to provide this feature as a third-party library and was careful not to rely on string comparison for messages, but rather to use the information provided by errorInfo as much as possible. unique-violation-detector/src/MySQLDetector.php at master · mpyw/unique-violation-detector
I think the impact of the breaking changes is minimal, so I don’t see any problem with having a pull request that implements these changes. If you have the motivation, I think it would be great to submit one. |
I have summarized the exchanges so far on Zenn, a Japanese technical article sharing site. I hope that non-Japanese speakers would read it through translation. [Laravel] createOrFirst の登場から激変した firstOrCreate, updateOrCreate に迫る! |
Added
mariadb
,postgres
, andmssql
container images to the localdocker-compose.yml
to ease local testing. They are commented out because we may not always need them locally, but they are handy to have around when dealing with Database/Eloquent featuresUniqueConstraintViolationException
that is a sub-class ofQueryException
whenever a unique constraint violation is detectedcreateOrFirst
method to the Eloquent Builder and RelationscreateOrRestore
macro to the soft deleting scopematchesUniqueConstraintException
protected method to the baseConnection
class. The default implementation returnsfalse
as there's no way to generalize the unique constraint violation detection to all drivers. It's up to each child class ofConnection
to implement the correct way to detect UNIQUE constraint violations. The drivers Laravel ships by default all implemented this method. Custom drivers must implement it if they want to support the usage ofcreateOrFirst
. Not implementing it should only be an issue when users of the custom driver attempt to usecreateOrFirst
, since it will throw an exception instead of the correct behavior (which should be easy to catch with tests).Changed
firstOrCreate
method now uses thecreateOrFirst
method under the hood (instead ofcreate
) to fix the race condition in thefirstOrCreate
method. It now tries to find the model. If it's missing, it tries to create. If a unique constraint violation happens when trying to create, it will attempt another find.Context
This method is similar to the
firstOrCreate
, but should work better in highly concurrent environments. WithfirstOrCreate
we may run into a race condition as two processes may not see an existing record created if they are running at the same time (or when you use read/write replicas and your read-replica is catching up), and when both attempt to create the record they would unexpected errors. Either one of the attempts to create would throw an exception unexpectedly due to aUNIQUE
constraint violation if there's one in the table, or this race condition would generate duplicate entries if the UNIQUE constraint is missing.With
createOrFirst
, we invert this flow and rely on the tables having aUNIQUE
constraint. So, first, we attempt to create the record, and if we get an exception back from the database and identify that it's a unique constraint violation, we attempt to find the matching record instead. This way, concurrent processes may rely on the ACID characteristics of the database and never have to worry about that race condition again.With that being said, the
createOrFirst
method does not replacefirstOrCreate
entirely as it relies on having UNIQUE constraints in the database, which the latter does not.To detect that we ran into a UNIQUE constraint violation, we are matching against the exception message:
1066
, but for some reason, the$exception->getCode()
was returning23000
, so instead of matching on the code, I went with matching against the error message (see here, look for "ER_NONUNIQ_TABLE").23505
error message for unique constraint violations (here).