PHPackages                             c-tanner/laravel-deep-sync - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. c-tanner/laravel-deep-sync

ActiveLibrary

c-tanner/laravel-deep-sync
==========================

Elegantly sync properties across any relationship

v0.2.0(1y ago)202MITPHPPHP ^8.2

Since Sep 29Pushed 1y ago1 watchersCompare

[ Source](https://github.com/c-tanner/laravel-deep-sync)[ Packagist](https://packagist.org/packages/c-tanner/laravel-deep-sync)[ RSS](/packages/c-tanner-laravel-deep-sync/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (5)Versions (3)Used By (0)

[![build](https://github.com/c-tanner/laravel-deep-sync/actions/workflows/build.yml/badge.svg)](https://github.com/c-tanner/laravel-deep-sync/actions/workflows/build.yml)[![PHP 8.2/3](https://github.com/c-tanner/laravel-deep-sync/actions/workflows/php-compatibility.yml/badge.svg)](https://github.com/c-tanner/laravel-deep-sync/actions/workflows/php-compatibility.yml)[![Laravel 10/11](https://github.com/c-tanner/laravel-deep-sync/actions/workflows/laravel-compatibility.yml/badge.svg)](https://github.com/c-tanner/laravel-deep-sync/actions/workflows/laravel-compatibility.yml)

Laravel Deep Sync
=================

[](#laravel-deep-sync)

Elegantly sync properties across any relationship.

Installation
------------

[](#installation)

Requirements:

- PHP &gt;= 8.2
- Laravel &gt;= 10

`composer require c-tanner/laravel-deep-sync`

More than just cascading soft-deletes
-------------------------------------

[](#more-than-just-cascading-soft-deletes)

Cascading soft-deletes within Laravel has been covered by a number of great packages in the past. At its core, though, `deleted_at` is just another class property.

While DeepSync does offer native support for cascading / syncing soft-deletes, you can also assign *any* model property as `syncable` - and choose which models should follow suit.

Let's take the classic `User` / `Post` example:

```
#[ObservedBy([DeepSync::class])]
class User extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = [
        'name',
        'is_active'
    ];

    // Properties that trigger DeepSync
    public $syncable = ['is_active'];

    #[SyncTo]
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class, 'author_id');
    }
}
```

Here, our `User` model defines it's `is_active` property as `syncable`, and that the `Post` model should `SyncTo` changes.

Then, in our `Post` model:

```
#[ObservedBy([DeepSync::class])]
class Post extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = [
        'title',
        'body',
        'author_id',
        'is_active'
    ];

    // Properties that trigger DeepSync
    public $syncable = ['is_active'];

    #[SyncFrom]
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class, 'author_id');
    }
}
```

> Note that the `Post` model must contain the `#[SyncFrom]` attribute, the `is_active` class property, and the `$syncable` array.

Observer events
---------------

[](#observer-events)

DeepSync currently supports `saved()` and `deleted()` model events. Note that in Laravel, `update()` also calls `save()` under the hood, and will also trigger the DeepSync observer.

Polymorphic support
-------------------

[](#polymorphic-support)

Cascading properties in one-to-one or one-to-many relationships is straightforward: when the "parent" model changes state, DeepSync finds the "child" records using Eloquent relationship methods tagged with the `#[SyncTo]` attribute and updates the property to the same value. Child models are also inspected for their relationship methods, and the process continues down the tree.

For many-to-many or many-to-one relationships, DeepSync only updates child records *if all parents share the same state*.

[![example relationship diagram](https://github.com/c-tanner/laravel-deep-sync/raw/main/doc/relationship-example-1.png)](https://github.com/c-tanner/laravel-deep-sync/blob/main/doc/relationship-example-1.png)

In the example above, we can see that when User A is deleted, Post A is also deleted, as User A is it's only parent. Since Post B, even though it also related to User A, is also related to User B, and therefore remains unchanged.

DeepSync relationships cascade, and will traverse to as many levels as are defined:

[![example multi-level relationship diagram](https://github.com/c-tanner/laravel-deep-sync/raw/main/doc/relationship-example-2.png)](https://github.com/c-tanner/laravel-deep-sync/blob/main/doc/relationship-example-2.png)

Though these examples use delete actions for ease of demonstration, these concepts apply to all class properties defined in the `syncable` array.

Omnidirectional syncs
---------------------

[](#omnidirectional-syncs)

Because we can define the direction of `SyncFrom` and `SyncTo` independent of our actual class hierarchy, a pretty neat feature becomes available.

Let's say we have two models, `Task` and `Subtask`. The class hierarchy is as you would expect:

```
class Task {
    return subtasks(): HasMany
        return $this->hasMany(Subtask::class);
    }
}
```

However, let's say that both classes have a property, `is_complete`, which defaults to `false`, and we want to automatically mark a `Task` complete only when all related `Subtasks` are also complete:

[![example reverse sync diagram](https://github.com/c-tanner/laravel-deep-sync/raw/main/doc/relationship-example-3.png)](https://github.com/c-tanner/laravel-deep-sync/blob/main/doc/relationship-example-3.png)

Let's look at how to acheive this in the code:

```
#[ObservedBy([DeepSync::class])]
class Task extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = [
        'name',
        'is_complete'
    ];

    public $syncable = ['is_complete'];

    #[SyncFrom]
    public function subtasks(): HasMany
    {
        return $this->hasMany(Subtask::class);
    }
}
```

> Note that we are using the `#[SyncFrom]` attribute on the "parent" class here instead of `#[SyncTo]`.

And in our `Subtask` class:

```
#[ObservedBy([DeepSync::class])]
class Subtask extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $fillable = [
        'name',
        'is_complete',
        'task_id'
    ];

    public $syncable = ['is_complete'];

    #[SyncTo]
    public function task(): BelongsTo
    {
        return $this->belongsTo(Task::class);
    }
}
```

Now let's test it:

```
public function test_reverse_sync()
{
    $task = Task::factory()->has(
        Subtask::factory(3)->state(
            function(array $attributes, Task $task) {
                return [
                    'task_id' => $task->id
                ];
            }
        )
    )->create();

    $this->assertEquals(1, Task::count());
    $this->assertEquals(3, Subtask::count());
    $this->assertEquals(3, Task::find($task->id)->subtasks()->count());

    // Task only becomes complete when all subtasks are complete

    $subtask1 = Subtask::find(1);
    $subtask1->update(['is_complete' => 1]);

    $this->assertEquals(0, Task::find($task->id)->is_complete);

    $subtask2 = Subtask::find(2);
    $subtask2->update(['is_complete' => 1]);

    $this->assertEquals(0, Task::find($task->id)->is_complete);

    $subtask3 = Subtask::find(3);
    $subtask3->update(['is_complete' => 1]);

    $this->assertEquals(1, Task::find($task->id)->is_complete);

}
```

```
$ ~/laravel-deep-sync: vendor/bin/phpunit --testsuite=Feature --colors=always
PHPUnit 11.3.6 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.11
Configuration: /Users/christanner/Code/laravel-deep-sync/phpunit.xml

.                                                                   1 / 1 (100%)

Time: 00:00.238, Memory: 38.50 MB

OK (1 test, 6 assertions)

```

Configuration
-------------

[](#configuration)

Ironically, Observers in Laravel aren't very observable (I think that's what irony is, right?). This can make debugging quite difficult, so DeepSync comes with verbose logging configured by default, output to your application's default log channel. You can turn logging off, or change the log severity by publishing the configuration file:

`php artisan vendor:publish --tag=deepsync`

###  Health Score

26

—

LowBetter than 43% of packages

Maintenance36

Infrequent updates — may be unmaintained

Popularity10

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

587d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/56940cd5465a9303149d59903b752297d6353a06ec88b4ca3a51fe8197626f70?d=identicon)[c-tanner](/maintainers/c-tanner)

---

Top Contributors

[![c-tanner](https://avatars.githubusercontent.com/u/55005098?v=4)](https://github.com/c-tanner "c-tanner (83 commits)")

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/c-tanner-laravel-deep-sync/health.svg)

```
[![Health](https://phpackages.com/badges/c-tanner-laravel-deep-sync/health.svg)](https://phpackages.com/packages/c-tanner-laravel-deep-sync)
```

###  Alternatives

[fumeapp/modeltyper

Generate TypeScript interfaces from Laravel Models

196277.9k](/packages/fumeapp-modeltyper)[slowlyo/owl-admin

基于 laravel、amis 开发的后台框架~

61214.2k26](/packages/slowlyo-owl-admin)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
