Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Laravel’s $touches Property: Automatic Parent Timestamp Updates

Laravel's $touches property automatically updates parent model timestamps when child models are modified.

Basic Usage

class Comment extends Model
{
    protected $touches = ['post'];

    public function post()
    {
        return $this->belongsTo(Post::class, 'post_id');
    }
}

When any Comment instance is created, updated, or deleted, Laravel automatically updates the updated_at timestamp of its parent Post model.

How It Works

  1. Laravel intercepts save/delete operations on the child model
  2. Checks the $touches property for relationship names
  3. Calls touch() on each specified relationship
  4. The parent model's updated_at timestamp is updated with the current time

Implementation Example: Cache Invalidation

Problem

Cache keys need to change when related data is modified to prevent serving stale data.

Solution

Include parent's updated_at timestamp in cache keys and use $touches on child models:

// Parent model
class Post extends Model
{
    public function comments() {
        return $this->hasMany(Comment::class);
    }
}

// Child model
class Comment extends Model
{
    protected $touches = ['post'];

    public function post() {
        return $this->belongsTo(Post::class);
    }
}

// Cache service
class CacheService
{
    protected function generateKey($post)
    {
        return sprintf('post:%d:v%s', $post->id, $post->updated_at->timestamp);
    }
}

Result: Modifying any comment automatically changes the parent's timestamp, which changes the cache key, causing cache misses for stale data.

Works Without Child Timestamps

Child models don't require their own timestamps for $touches to function:

class PostTag extends Model
{
    public $timestamps = false;  // No created_at/updated_at columns
    protected $touches = ['post'];  // Still updates parent timestamp
}

Multiple Parent Relationships

Multiple relationships can be touched simultaneously:

class Attachment extends Model
{
    protected $touches = ['post', 'author'];

    public function post() {
        return $this->belongsTo(Post::class);
    }

    public function author() {
        return $this->belongsTo(User::class);
    }
}

Both post and author models will have their timestamps updated when the attachment changes.

Disabling Touch Temporarily

For bulk operations where touching is undesirable:

Model::withoutTouching(function () {
    // Operations here won't trigger touches
    Comment::where('post_id', 1)->update(['status' => 'approved']);
});

Or disable globally for a model:

Comment::withoutTouching(function () {
    // All Comment operations won't touch parents
});

Performance Considerations

Each touch operation executes an additional UPDATE query:

UPDATE posts SET updated_at = '2024-01-15 10:30:00' WHERE id = 1

Performance impact:

  • Single operations: Negligible (one extra query)
  • Bulk operations: Can be significant (N extra queries for N updates)
  • Mitigation: Use withoutTouching() for bulk updates

Database Requirements

  • Parent model table must have updated_at column
  • Parent model must have $timestamps = true (default)
  • Child model's foreign key must be properly indexed for performance

Use Cases

Appropriate Uses

  • Cache key generation based on last modification
  • Audit trails and activity tracking
  • Search index updates
  • API response ETags
  • Version control systems

Inappropriate Uses

  • High-frequency child updates (causes parent timestamp thrashing)
  • When parent modification tracking should be independent
  • Complex invalidation logic requiring specific conditions

Technical Details

Under the Hood

Laravel's implementation uses the touchOwners() method in Illuminate\Database\Eloquent\Model:

public function touchOwners()
{
    foreach ($this->touches as $relation) {
        $this->$relation()->touch();

        if ($this->$relation instanceof self) {
            $this->$relation->fireModelEvent('saved', false);
            $this->$relation->touchOwners();
        }
    }
}

Events

Touching triggers the following events on the parent model:

  • saving
  • updating
  • saved
  • updated

Cascading Touches

Touches cascade through multiple levels if intermediate models also have $touches defined:

// Comment -> Post -> Blog
class Comment extends Model
{
    protected $touches = ['post'];
}

class Post extends Model
{
    protected $touches = ['blog'];
}

Modifying Comment updates both Post and Blog timestamps.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Subscribe to my newsletter

Get new posts when I publish them.

I respect your privacy. Unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *