Skip to content

[13.x] Add refreshForUpdate() method to Eloquent models - #61247

Merged
taylorotwell merged 1 commit into
laravel:13.xfrom
stevebauman:refresh-for-update
Aug 20, 2026
Merged

taylorotwell merged 1 commit into
laravel:13.xfrom
stevebauman:refresh-for-update

Conversation

@stevebauman

Copy link
Copy Markdown
Contributor

This PR adds a refreshForUpdate() method to Eloquent models -- solving a bit of a convenience paper cut that I've experienced for a few years and never got around to PR'ing.

Models are often loaded before a transaction begins (ex. through route model binding). Acquiring a pessimistic lock currently requires us to create another query, look up the model by its primary key, and replace the existing instance.

For example, safely reducing a product's available stock currently requires:

public function purchase(Product $product): Response
{
    DB::transaction(function () use ($product) {
        $product = Product::query()
            ->lockForUpdate()
            ->findOrFail($product->getKey());

        if ($product->stock === 0) {
            throw new RuntimeException('The product is out of stock.');
        }

        $product->decrement('stock');
    });

    // ...
}

With this change, the existing model can be refreshed and locked directly:

public function purchase(Product $product): Response
{
    DB::transaction(function () use ($product) {
        $product->refreshForUpdate();

        if ($product->stock === 0) {
            throw new RuntimeException('The product is out of stock.');
        }

        $product->decrement('stock');
    });

    // ...
}

The method behaves like refresh(), but applies lockForUpdate() to the query used to reload the model. It refreshes the existing instance with the current database attributes, allowing it to be used without reassignment for the remainder of the transaction.

Let me know your thoughts! ❤️

@taylorotwell
taylorotwell merged commit d69891d into laravel:13.x Aug 20, 2026
23 of 56 checks passed
binaryfire added a commit to hypervel/components-backup that referenced this pull request Oct 1, 2026
Port refreshForUpdate with the shared refresh query path, upstream unit and integration coverage, and usage documentation. Preserve model key validation and relationship reload behavior.

Make relationship touching honor WithoutTimestamps and the same property/attribute precedence as model initialization. Disabled models could otherwise write updated_at, including on tables without that column. Strengthen the precedence cases and reconcile both attribute cache-order tests against the existing instance-based cache.

Complete the missing multi-attribute touch model test and documentation. Use the upstream keyed AsPivot lookup in the moved refresh helper; the rest of the trait lookup PR remains under reconciliation.

Upstream: laravel/framework at 01d008c9b5f32cb7c5e50a9a22273113d810b2a2.

laravel/framework#61247
laravel/framework#59175
laravel/framework#61073
laravel/framework#60815
laravel/framework#59457

Validation: timestamp precedence regressions failed before the correction and pass afterward; SQLite relation touching verifies inherited disabling. Changed test files, affected Eloquent ParaTest, full source/type analysis, and scoped formatting pass. Refresh integration tests also pass after the final trait lookup correction.
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.

2 participants