[13.x] Fix incrementEach/decrementEach to scope to model instance#59376
Merged
taylorotwell merged 3 commits intolaravel:13.xfrom Mar 26, 2026
Merged
[13.x] Fix incrementEach/decrementEach to scope to model instance#59376taylorotwell merged 3 commits intolaravel:13.xfrom
taylorotwell merged 3 commits intolaravel:13.xfrom
Conversation
|
Thanks for submitting a PR! Note that draft PRs are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface. Pull requests that are abandoned in draft may be closed due to inactivity. |
a41922b to
beedcef
Compare
…odel instance Fixes laravel#57262. Model-level: Adds incrementEach/decrementEach methods that scope to the model's primary key, fire updating/updated events, and sync in-memory attributes — mirroring the existing incrementOrDecrement pattern. Builder-level: Adds incrementEach/decrementEach methods that call addUpdatedAtColumn, ensuring updated_at is automatically set — consistent with how increment/decrement already behave. Co-Authored-By: sumaiazaman <saktar50.cse@gmail.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add isClassDeviable/deviateClassCastableAttribute handling per column, matching incrementOrDecrement behavior for Money/custom cast objects - Use syncOriginalAttributes(array_keys($columns)) instead of syncOriginal() to avoid marking unrelated dirty attributes as clean - Set attributes before event check, matching increment() behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
729a3e1 to
aef4c87
Compare
Tests against a real database to verify: - Instance call only affects that row (other rows unchanged) - decrementEach only affects that row - Query builder path still affects all matching rows - Timestamps are updated automatically - Soft-deleted models work (ignores global scopes) - Unrelated dirty attributes are preserved - Changes and previous values sync correctly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
Summary
Fixes 🟢 #57262. Also addresses 🔴 #48595, 🔴 #49009, 🔴 #52419, 🔴 #57302.
Aligns
$model->incrementEach()and$model->decrementEach()behavior with$model->increment()and$model->decrement()when called on a model instance. Currently, callingincrementEachon an instance forwards to the base Query Builder without scoping to the model's primary key. This PR adds the Model and Builder-level methods to match the existingincrement/decrementpattern.Before
After
Query builder usage is unchanged:
Changes
Model-level (
Model.php):incrementEach(),decrementEach(), andincrementOrDecrementEach()methods mirroring the existingincrementOrDecrement()patternsetKeysForSaveQuery()isClassDeviable()/deviateClassCastableAttribute()updating/updatedmodel eventssyncOriginalAttributes()scoped to only the affected columns__call()so model instance calls are interceptedBuilder-level (
Builder.php):incrementEach()anddecrementEach()that calladdUpdatedAtColumn()updated_atis automatically set, consistent withincrement()/decrement()Attribution
Builds on @sumaiazaman's draft ⚪ #59065.
Test plan
Integration tests (7 tests in
EloquentUpdateTest.php— real database):testIncrementEachOnModelInstanceOnlyAffectsThatRow— 3 rows, only target row changestestDecrementEachOnModelInstanceOnlyAffectsThatRow— 2 rows, only target row changestestIncrementEachViaQueryBuilderStillAffectsAllMatchingRows— mass update unchangedtestIncrementEachOnModelInstanceUpdatesTimestamps— updated_at automatically settestIncrementEachOnSoftDeletedModelIgnoresGlobalScopes— works on trashed modelstestIncrementEachDoesNotResetUnrelatedDirtyAttributes— dirty name preserved after incrementing viewstestIncrementEachSyncsPrevious— getChanges() reflects incremented columnsUnit tests — Model (6 tests in
DatabaseEloquentModelTest.php):testIncrementEachOnExistingModelScopesQueryToModelKey— WHERE clause + attribute synctestDecrementEachOnExistingModelScopesQueryToModelKey— WHERE clause + attribute synctestIncrementEachWithExtraColumnsOnExistingModel— $extra parameters propagatedtestIncrementEachFiresModelEvents— updating/updated events firetestIncrementEachReturnsFalseWhenUpdatingEventCancelled— event cancellation respectedtestIncrementEachOnNonExistingModelForwardsToQueryBuilder— non-existing model pathUnit tests — Builder (3 tests in
DatabaseEloquentBuilderTest.php):testIncrementEachCallsToBaseWithUpdatedAt— updated_at automatically addedtestDecrementEachCallsToBaseWithUpdatedAt— updated_at automatically addedtestIncrementEachWithoutTimestamps— models without timestamps skip updated_atPost-merge
incrementEach/decrementEachto the Eloquent docs (currently draft, blocked by this PR)