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

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:
- Product – Represents items like rings or necklaces with a base price (e.g., $10 for a gold band)
- 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.

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
protected static function boot()
– This method is called when the model is booted (initialized)parent::boot()
– We call the parent boot method to ensure all parent functionality is preservedstatic::saved(function ($diamond) {...}
– Register a closure that runs every time a Diamond is saved (created or updated)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 databasewasChanged()
checks if an attribute was changed during the save operation
$diamond->products()->chunk(100, function ($products) {...}
– Load related products in chunks of 100 to prevent memory issues with large datasets$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:
- We have a gold ring (Product) with a base price of $10
- We attach a 1-carat diamond (Diamond) priced at $20
- 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:
- The
saved
event fires in the Diamond model - Our code detects the price change
- All products with this diamond are updated in chunks of 100
- Each product’s
save()
triggers its ownsaving
event - 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