Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Synchronizer Laravel Package

ruwork/synchronizer

A Laravel package that simplifies synchronizing related Eloquent models and their pivot data, letting you keep associated records consistent through a single declarative call instead of manual attach, detach, and update logic.

View on GitHub
Deep Wiki
Context7

Getting Started

ruwork/synchronizer is a small, dormant MIT-licensed utility from the RuVents team. It is not a Laravel-first package in the way spatie/laravel-* packages are, so treat it as a low-level library you wire into your own application code rather than something that "just works" after composer require. The only sign of life in years is the 0.2.0 release, which is a narrow internal change — not a revival, but it does tell you exactly where the package's extension seam is.

Minimal steps:

  1. Install and inspect before integrating.

    composer require ruwork/synchronizer:0.2.0
    

    Then open vendor/ruwork/synchronizer/src/ immediately. With a package this small and this old there is no substitute for reading the ~handful of classes yourself — the README on GitHub is terse, and the source is the real documentation. Identify the core synchronizer class, the interface it implements (if any), and what it expects as input (arrays, iterables, entities, DTOs).

  2. Read TypeInterface first. The 0.2.0 change lands in TypeInterface::configure(), so the "type" abstraction is the package's real API surface: you describe how a field/collection is read, written and compared via type objects, and the synchronizer drives them. Start with TypeInterface and its bundled implementations, then work outward to the synchronizer that consumes them — that path explains 90% of the behaviour.

  3. Check framework compatibility. The package predates current Laravel and targets PHP 7.x-era conventions. Verify the composer.json constraints and whether service-provider auto-discovery works. If auto-discovery fails, register the provider manually in config/app.php (Laravel ≤10) or bootstrap/providers.php (Laravel 11).

  4. Find your first use case. The natural fit is declarative collection syncing: you have a parent model with a set of related records (tags, roles, line items, permissions) and an incoming payload of IDs or attributes, and you want "make the database match this payload" in one call — inserts, updates, and deletes — instead of hand-rolling the diff logic. Start there, with a single relation and the simplest built-in type, on a throwaway branch.

  5. Write a smoke test before production. Because the package has effectively zero community usage (0 stars, 0 dependents), you own the risk. A 20-line feature test that syncs a known payload and asserts the resulting rows is worth more than any amount of documentation reading.

Implementation Patterns

Service-layer wrapper. Do not call the synchronizer directly from controllers. Wrap it in an application service so the dependency is isolated and swappable:

final class SyncProjectMembers
{
    public function __construct(private Project $project) {}

    public function handle(array $memberIds): void
    {
        DB::transaction(function () use ($memberIds) {
            // build the type(s), then call into ruwork/synchronizer here
        });
    }
}

This matters more than usual: if the package breaks on a future PHP/Laravel upgrade, you replace the body of one method rather than hunting call sites.

Always wrap in a transaction. Synchronizers by nature perform multiple writes (create / update / delete). Any partial failure leaves your relations inconsistent. DB::transaction() around the sync call is non-negotiable — and if the package does not already do so, add lockForUpdate() on the parent row when concurrent requests are plausible.

Validate into a canonical payload first. Normalize request input through a FormRequest so the synchronizer always receives the same shape (deduplicated, cast to int, sorted). Diffing logic that receives ["3", 3, "03"] vs [3] will behave unpredictably; sorting and unique-ing the ID list before the call also makes "did anything change?" short-circuits trivial.

Sync in queued jobs for large collections. If a user can submit hundreds of relations, move the sync into a job. The pattern becomes: controller validates → dispatch job → job resolves the model fresh and runs the sync inside a transaction. This also gives you retries for free.

Use model events as the integration seam. If the synchronizer fires model events (or if you hook your own observers on the related models), you can hang side effects — reindexing in Scout, cache busting, audit logging — off the same path for both manual edits and bulk syncs. Otherwise your search index drifts the moment someone uses the bulk path.

Pair with an explicit "unchanged" guard. Most synchronizers will happily run update on rows whose attributes did not change, bumping updated_at across the whole set. If the package does not compare attributes, do it yourself: filter the payload to rows that actually differ before handing it over, or your audit trail and cache invalidation will be noisy.

Composition over inheritance. If the package exposes a base synchronizer or base type class to extend, prefer composition (inject and delegate) in anything you intend to unit test, so you are not mocking a 2018 hierarchy.

Use the new context parameter deliberately. Since 0.2.0, TypeInterface::configure() receives context. Pass in the few things your types actually need (parent model, request/user, mode flag) rather than a grab-bag array, and keep custom types stateless-ish: the same type class can now be configured differently per call site, so never assume one configuration per process.

Gotchas and Tips

  • 0.2.0 is a BC break for custom types, and nothing else. The release changes the configure() signature on TypeInterface. If you wrote your own type implementations against 0.1.x, they will fatal until you accept the context argument. If you only use bundled types, the upgrade is likely a no-op — but read the diff, because "pass context" may also mean the synchronizer now supplies something your subclasses were previously deriving themselves.
  • Abandonment risk is still the headline issue. One narrow release does not mean maintenance: no dependents and no issue traffic means bugs sit unfixed. Pin the version in composer.json (exact constraint, e.g. 0.2.0, not ^0.2) and add a composer.lock review to your upgrade checklist.
  • PHP 8 compatibility is unverified. Type coercion, Iterator/ArrayAccess signatures, and each()-era code are common breakages in packages of this vintage. Adding a parameter to an interface method in 0.2.0 suggests the signature hygiene here is loose; run your test suite on the target PHP version before committing.
  • Laravel service provider auto-discovery. If the package predates the extra.laravel.providers convention or ships it incorrectly, you will get "target class does not exist" errors — register manually and move on.
  • Config publishing is probably absent. Do not assume php artisan vendor:publish exposes a config file; grep for publishes( in the provider. If there is no config, hardcode behavior in your wrapper rather than editing vendor code.
  • Silent no-ops are the classic bug. The two failure modes of any synchronizer are (a) deleting too much because an empty payload was interpreted as "remove all", and (b) deleting nothing because keys did not match (string vs int, or wrong key column). Add a guard for empty payloads in your service and log the resulting create/update/delete counts.
  • Log counts, not payloads. Emit created=3 updated=2 deleted=1 at debug level around the call. When a customer reports "my tags vanished," those three numbers tell you instantly which failure mode you hit.
  • Check for sync() naming collisions. If the package's method is called sync, it is easy to confuse with Eloquent's BelongsToMany::sync(), which has different semantics (it does not delete by default). Be explicit in comments about which one a call site uses.
  • Extension points: TypeInterface is the one that matters. With a package this small, the realistic extension points are custom types implementing TypeInterface, an interface you bind in your own container, a decorator around the synchronizer, or model observers on the affected models. Do not count on documented hooks — 0.2.0 is the clearest evidence yet that types, not synchronizers, are where the author expects you to plug in.
  • If you already forked for 0.1.x, port the context change rather than skipping it. Cherry-picking the configure() signature keeps your fork diffable against upstream, which is the only way a later upstream fix (unlikely though it is) stays cheap to take.
  • Fork if you depend on it long-term. Given the MIT license and the size of the codebase, vendoring the source into app/Support/Synchronizer (or a private fork) is a legitimate and probably wise move — it removes the upgrade-blocking dependency and lets you add the transaction/guard logic the package lacks.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo