xedi/eloquent-sync
xedi/eloquent-sync is a Laravel package for keeping Eloquent models synchronized with external systems. It helps map model data, push and pull changes, and keep local records consistent with remote sources, reducing custom integration boilerplate for sync workflows.
composer require xedi/eloquent-sync. There is no Laravel auto-discovery guarantee on modern versions — if the service provider isn't registered automatically, add it manually in config/app.php.vendor/xedi/eloquent-sync/src) before trusting the README. With 1 star, 0 dependents, and a last release from June 2020, the code is the source of truth — confirm the trait name, the method signature, and whether sync() is exposed on the relationship or on the parent model.BelongsToMany::sync(): attach it to a parent model that owns a hasMany, then call:
$user->posts()->sync([$postA->id, $postB->id]);
or, if the package exposes it on the model instead of the relationship:
$user->sync('posts', [$postA->id, $postB->id]);
'post_ids' => ['array'], 'post_ids.*' => ['integer', 'exists:posts,id']), then call sync() inside a DB::transaction(...) so a partial sync can't leave orphaned or misassigned rows.hasMany you intend to sync. Keep it centralized in a BaseModel only if you're confident the trait doesn't override conflicting relationship behaviour.hasMany doesn't ship with sync(). Expect the implementation to update the foreign key on the given IDs to the parent and nullify/delete the ones that fall out. Mirror that expectation in your tests.firstOrCreate loops when syncing new children, not just existing ones — the package syncs existing rows by key; creation is usually your responsibility:
DB::transaction(function () use ($user, $data) {
$ids = collect($data)->map(fn ($p) => $user->posts()->firstOrCreate(['slug' => $p['slug']], $p)->id);
$user->posts()->sync($ids);
});
post_ids array, validate, sync, then $user->load('posts') and return the fresh relation. Because the underlying foreign key updates may bypass model events (see gotchas), reload from the DB rather than trusting in-memory state.collect($ids)->chunk(500)) — most implementations issue per-row updates, which is fine for form-sized data and painful for bulk imports.Illuminate version constraints in composer.json. If it pins Laravel 5.x/6.x, you're on your own for 8/9/10/11 support. Pin the dependency and write a contract test covering the exact sync semantics you rely on, so a future framework upgrade fails loudly rather than silently mangling foreign keys.sync() is destructive. Any child row whose ID isn't in the array will be detached or deleted depending on the implementation. Never call it without DB::transaction and never pass user input straight through without an exists validation rule — otherwise a malformed payload quietly wipes a relation.update()/delete(), saved, updated, and deleted events on the child models will be skipped, breaking observers, search indexing, cache invalidation, and activity logging. Watch a child model's updating/deleting hooks in a tinker session to confirm what actually fires before wiring observers.SoftDeletes and the package bypasses Eloquent, you can get permanently truncated rows where you expected a soft delete. This is the difference between a recoverable mistake and a data incident.onDelete('cascade') or a manual cleanup. Check your migration before trusting the package's default behaviour.DB::listen output — you'll see exactly which rows the package touches and in what order. If sync silently no-ops, the most common causes are a missing trait on the parent, the relationship not being named exactly as the package expects, or a guard/fillable issue preventing the foreign key from being set.update the kept IDs, whereNotIn(...)->delete() the rest) is often a more durable choice than a 2020 dependency — evaluate it against your actual risk tolerance before shipping.How can I help you explore Laravel packages today?