Laravel Model Syncing: The Ultimate Guide to Auto-Updating Related Models

TIMESTAMP: 2025.03.05 21:36
AUTHOR:
CATEGORY: Programming

Laravel Model Syncing is essential when creating applications that deal with complex model relationships. In this post, I’ll share a powerful technique for automatically synchronizing price changes between related models using Laravel’s model events.

The Business Problem

This is where Laravel Model Syncing becomes valuable. Let’s consider an e-commerce jewelry application with two main models:

  1. Product – Represents items like rings or necklaces with a base price (e.g., $10 for a gold band)
  2. Diamond – Represents diamonds that can be attached to products, each with its own price

When a diamond’s price changes, we need to update all products that have this diamond attached to ensure prices remain accurate throughout the system.

Laravel Model Syncing

The Solution: Laravel Model Syncing with Events

Laravel provides a powerful event system through model boot methods that lets us hook into model lifecycles. Here’s how we implement Laravel Model Syncing in our Diamond model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Diamond extends Model
{
    protected $fillable = ['name', 'carat', 'price', 'clarity'];

    protected static function boot()
    {
        parent::boot();
        
        static::saved(function ($diamond) {
            // Check if price was changed
            if ($diamond->isDirty('price') || $diamond->wasChanged('price')) {
                // Get all products that use this diamond and update them immediately
                $diamond->products()->chunk(100, function ($products) {
                    foreach ($products as $product) {
                        // This will trigger the saving event that recalculates prices
                        $product->save();
                    }
                });
            }
        });
    }

    /**
     * Get the products that use this diamond
     */
    public function products()
    {
        return $this->belongsToMany(Product::class);
    }
}

How It Works

  1. protected static function boot() – This method is called when the model is booted (initialized)
  2. parent::boot() – We call the parent boot method to ensure all parent functionality is preserved
  3. static::saved(function ($diamond) {...} – Register a closure that runs every time a Diamond is saved (created or updated)
  4. if ($diamond->isDirty('price') || $diamond->wasChanged('price')) – Check if the price attribute was modified:
    • isDirty() checks if an attribute was modified but not yet saved to the database
    • wasChanged() checks if an attribute was changed during the save operation
  5. $diamond->products()->chunk(100, function ($products) {...} – Load related products in chunks of 100 to prevent memory issues with large datasets
  6. $product->save() – Re-save each product to trigger its own updating logic

The Product Model

To complete the picture, here’s how the Product model might be set up:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = ['name', 'base_price', 'total_price', 'description'];
    
    /**
     * Get the diamonds attached to this product
     */
    public function diamonds()
    {
        return $this->belongsToMany(Diamond::class);
    }
    
    /**
     * Calculate the total price of the product including diamonds
     */
    protected static function boot()
    {
        parent::boot();
        
        static::saving(function ($product) {
            // Calculate total price as base_price + sum of all attached diamonds
            $diamondTotal = $product->diamonds()->sum('price');
            $product->total_price = $product->base_price + $diamondTotal;
        });
    }
}

A Real-World Laravel Model Syncing Example

Let’s walk through a concrete example:

  1. We have a gold ring (Product) with a base price of $10
  2. We attach a 1-carat diamond (Diamond) priced at $20
  3. The total price of the ring becomes $30 ($10 base + $20 diamond)

Now, if we update the diamond’s price to $40:

$diamond = Diamond::find(1);
$diamond->price = 40;
$diamond->save();

The following occurs:

  1. The saved event fires in the Diamond model
  2. Our code detects the price change
  3. All products with this diamond are updated in chunks of 100
  4. Each product’s save() triggers its own saving event
  5. Each product recalculates its total price ($10 base + $40 diamond = $50)

The total price of our ring is now automatically updated to $50!

Performance Considerations

Notice how we use the chunk() method when updating products. This is critical for performance reasons:

$diamond->products()->chunk(100, function ($products) {
    foreach ($products as $product) {
        $product->save();
    }
});

If a diamond is used in thousands of products, loading all related products at once could cause memory issues. Chunking processes them in batches, keeping memory usage low while ensuring all products are updated. This approach to Laravel Model Syncing is performance-conscious.

Conclusion

This pattern provides a clean, maintainable way to keep related models in sync. By leveraging Laravel’s model events and relationships, we can ensure that changes in one model automatically propagate to related models without coupling our business logic to controllers or manual update processes.

This approach follows good design principles:

  • Single Responsibility – Each model handles its own concerns
  • DRY (Don’t Repeat Yourself) – No duplicate code across controllers
  • Maintainability – Business logic stays in the models where it belongs