Skip to content

[13.x] Report MultipleRecordsFoundException from sole()#60294

Merged
taylorotwell merged 1 commit into
laravel:13.xfrom
PeteBishwhip:13.x
May 27, 2026
Merged

[13.x] Report MultipleRecordsFoundException from sole()#60294
taylorotwell merged 1 commit into
laravel:13.xfrom
PeteBishwhip:13.x

Conversation

@PeteBishwhip
Copy link
Copy Markdown
Member

@PeteBishwhip PeteBishwhip commented May 27, 2026

MultipleRecordsFoundException got added to $internalDontReport in #35908 alongside RecordsNotFoundException, but the two aren't really the same kind of error and shouldn't be treated the same way.

RecordsNotFoundException makes sense in that list — prepareException() maps it to a NotFoundHttpException, so it surfaces as a 404. Silencing it from the log is fine, it's the same as any other route model binding miss.

MultipleRecordsFoundException isn't mapped anywhere. It just falls through as an unhandled 500. And because it's in $internalDontReport, nothing gets logged either. So when ->sole() matches more than one row, the user sees a generic 500 and there's no trace of it in the logs, Nightwatch, anywhere.

That's the exact case you'd want to know about. If you called ->sole() you're asserting there's exactly one row — finding two means your data or your query is wrong, and that's a real bug worth reporting. Right now it's getting swallowed.

This PR just removes it from the ignore list. 0 rows still 404s silently via RecordsNotFoundException. 2+ rows now reports like any other uncaught RuntimeException.

@PeteBishwhip PeteBishwhip marked this pull request as ready for review May 27, 2026 14:49
@laravel laravel deleted a comment from github-actions Bot May 27, 2026
@PeteBishwhip PeteBishwhip changed the title Report MultipleRecordsFoundException from sole() [13.x] Report MultipleRecordsFoundException from sole() May 27, 2026
@shaedrich
Copy link
Copy Markdown
Contributor

Wouldn't 409 work better as a status code? I mean, it's a client error like 404, not a server error, which isn't the type of problem that occurred?

@PeteBishwhip
Copy link
Copy Markdown
Member Author

Wouldn't 409 work better as a status code? I mean, it's a client error like 404, not a server error, which isn't the type of problem that occurred?

This exception is thrown when calling sole() from a model and it returns more than 1 record as only 1 is expected.

Model::query()->whereSomething('a_value')->sole();

A HTTP 409, by definition wouldn't fit here as it relates to mutation of data:

The 409 (Conflict) status code indicates that the request could not be completed due to a conflict with the current state of the target resource. This code is used in situations where the user might be able to resolve the conflict and resubmit the request. The server SHOULD generate content that includes enough information for a user to recognize the source of the conflict.

Conflicts are most likely to occur in response to a PUT request. For example, if versioning were being used and the representation being PUT included changes to a resource that conflict with those made by an earlier (third-party) request, the origin server might use a 409 response to indicate that it can't complete the request. In this case, the response representation would likely contain information useful for merging the differences based on the revision history.

Source: https://www.rfc-editor.org/rfc/rfc9110.html#name-409-conflict

This would be a Server Error as it's not something the user can resolve and isn't related to mutating data.

@shaedrich
Copy link
Copy Markdown
Contributor

But how can "1 too few" be a client error and "1 too many" be something else? Sole doesn't really care about whether its too few or too many—it throws because it is not one, that's the primary reason.

@PeteBishwhip
Copy link
Copy Markdown
Member Author

But how can "1 too few" be a client error and "1 too many" be something else? Sole doesn't really care about whether its too few or too many—it throws because it is not one, that's the primary reason.

"Model Not Found" is very different to "Expected 1 as defined, got more". That's why it would need to be a 500.

If you're using sole(), you're intending to only receive one result back. If you receive more, that's a bug. It's not going to be something the client can resolve unless you've got a very wide open endpoint at which point, you can use the Exception handler to translate this exception as you wish but in it's current state, it's simply swallowed without log entries.

@taylorotwell taylorotwell merged commit 02a6eb6 into laravel:13.x May 27, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants