Repository navigation
Add Laravel 13 support - #40
Merged
Merged
Conversation
Cover whereNot with scalar, closure and empty-array input, refresh() of mutated values, global scopes around nested where closures (the removed-scope case is version-aware for Laravel 13), related model connection inheritance, and wherePivot()->detach() on a relation without a custom pivot class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Laravel 13 needs PHP 8.3+, which Composer enforces per install, so the package's own PHP constraint stays at >=8.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Testbench deprecated getEnvironmentSetUp() in favour of defineEnvironment(), which exists in Testbench 8 through 11. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Laravel 13 sends an array given to whereNot() straight to the base query builder, so mutated columns were compared against unmutated values. Route the array through a negated nested where instead, using the same rules as Query\Builder::addArrayOfWheres(). This also changes behaviour on Laravel 10-12, where the array went through where() and lost both the negation and the boolean: whereNot(['id' => $id]) used to return the row it should exclude. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Laravel 13 reloads attributes in refreshForUpdate() and, for models that list $refreshes, after every save. Neither goes through refresh(), so mutated attributes kept returning their stale cached values. All of these paths, and refresh() on Laravel 10-13, end in setRawAttributes(), so clear the cache there and drop the refresh() override. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
newPivotQuery() only applied wherePivot() and wherePivotIn(), so detach(), sync(), toggle() and updateExistingPivot() ignored wherePivotNull() constraints and touched every pivot row of the parent. Laravel 13 also applies wherePivotBetween() there. Apply both, in Laravel's order. On Laravel 10-12 this narrows those operations when wherePivotNull() is used, matching Laravel itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts commit b37baae. The change is not needed for Laravel 13 and changes behaviour on Laravel 10-12, so it moves to its own PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Composer 2.9+ refuses to install versions that have security advisories. Every Laravel 10 and 11 release has one, so those CI legs failed at composer update before any test ran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Laravel 12 and 13 legs keep the check, since they have releases without advisories. Use COMPOSER_NO_BLOCKING, which replaces the deprecated COMPOSER_NO_SECURITY_BLOCKING in Composer 2.10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tlsqr
marked this pull request as ready for review
October 15, 2026 15:38
ChrGriffin
reviewed
Oct 15, 2026
| { | ||
| // Laravel 13 sends arrays straight to the base query builder, which would skip | ||
| // mutating the values. An empty array is left to the parent, because an empty | ||
| // nested where would make where() pop the previous clause. |
There was a problem hiding this comment.
are these comments really necessary?
| // mutating the values. An empty array is left to the parent, because an empty | ||
| // nested where would make where() pop the previous clause. | ||
| if (is_array($column) && $column !== []) { | ||
| // Same handling of an array of wheres as Query\Builder::addArrayOfWheres() |
| /** | ||
| * Every reload of attributes from the database ends here: refresh(), and on | ||
| * Laravel 13 also refreshForUpdate() and the attributes refreshed after save. | ||
| * |
| * Columns Laravel 13+ reloads from the database after every save. | ||
| * | ||
| * @var array<int, string> | ||
| */ |
| * Attach two second models through the relation without a custom pivot class: | ||
| * "Second null" with a null extra, "Second m" with extra "m". | ||
| */ | ||
| private function attachTwoPlain(): FirstModel |
There was a problem hiding this comment.
ditto re: comments. Maybe the function name could be a tad clearer, if verbose. attachTwoModelsWithoutPivot?
Review feedback: the comments restate what the tests and commit history already cover. Rename attachTwoPlain() and second_models_plain() to say what they lack: a custom pivot class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tlsqr
added a commit
to tlsqr/laravel-mutate
that referenced
this pull request
Oct 15, 2026
Matches the review feedback on Weebly#40: the comments restate what the tests already cover. Rename attachTwoPlain() and second_models_plain() to say what they lack: a custom pivot class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Laravel 13 records wherePivotBetween() constraints and applies them in newPivotQuery(). The override skipped them, so detach(), sync() and updateExistingPivot() touched every pivot row of the parent. Apply them after the whereIn constraints, as Laravel does. Laravel 10-12 don't record these constraints, so nothing changes there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tlsqr
added a commit
to tlsqr/laravel-mutate
that referenced
this pull request
Oct 15, 2026
Applying wherePivotBetween() in pivot queries only matters on Laravel 13, so it moves to Weebly#40 with its tests. This PR keeps the wherePivotNull() fix for Laravel 10-12. The constraint order test needs both loops, so it returns when this branch is rebased onto Weebly#40. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jholloman
approved these changes
Oct 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Laravel 13 support while keeping Laravel 10, 11 and 12.
Changes
composer.jsonallowslaravel/framework ^13.0andorchestra/testbench ^11.0. The package's PHP constraint stays at>=8.1, since Composer already enforces Laravel 13's PHP 8.3 minimum per install.COMPOSER_NO_BLOCKING=1on the Laravel 10 and 11 legs only. Composer refuses to install versions with security advisories, and every Laravel 10 and 11 release has one, so those legs failed before any test ran. This also affectsmaster. The Laravel 12 and 13 legs keep the check.EloquentBuilder::whereNot(): Laravel 13 sends an array argument straight to the base query builder, so mutated columns were compared against unmutated values (whereNot(['id' => $uuid])excluded nothing). The override routes arrays through a negated nested where, following the same rules asQuery\Builder::addArrayOfWheres().Model::setRawAttributes()now clears the mutator cache. Laravel 13'srefreshForUpdate()and$refreshes(attributes reloaded after save) bypassrefresh(), so mutated attributes kept their stale cached values.refresh(),refreshForUpdate()and$refreshesall end insetRawAttributes(), so therefresh()override is removed.BelongsToMany::newPivotQuery()applieswherePivotBetween()constraints. Laravel 13 records them and applies them in its ownnewPivotQuery(). The override skipped them, sowherePivotBetween(...)->detach(),sync()andupdateExistingPivot()touched every pivot row of the parent. Laravel 10-12 don't record these constraints, so nothing changes there.defineEnvironment()instead of the deprecatedgetEnvironmentSetUp().Behaviour change on Laravel 10-12
This is a bug fix, but consumers may notice it:
whereNot([...])now negates. Before, the array went throughwhere()and lost both thenotand the boolean, sowhereNot(['id' => $id])returned the row it should exclude, andorWhereNot([...])behaved asand.