ruwork/synchronizer-bundle
Symfony bundle for configurable data synchronization. Define sources, targets, and field mappings, then run repeatable sync operations to keep records consistent between your application and external systems.
Install — pin the bundle and the library it drives
composer require ruwork/synchronizer-bundle:^0.2
The 0.2.0 release is explicitly "Synchronizer 0.2.x support", so the bundle's only job is to wire ruwork/synchronizer 0.2.x into the container. Treat the pair as one unit:
composer show ruwork/synchronizer-bundle # confirm the exact tag
composer why ruwork/synchronizer # confirm which 0.2.x you actually get
composer why-not ruwork/synchronizer 0.2 # if resolution fails, this names the blocker
If your app already requires ruwork/synchronizer directly (common, because you want the classes for type-hints), constrain it to ^0.2 explicitly rather than letting the bundle's transitive constraint decide. Do not mix a 0.1.x synchronizer with the 0.2.0 bundle — see Gotchas.
Register the bundle — it is a classic Symfony bundle, so either add it to config/bundles.php:
Ruwork\SynchronizerBundle\RuworkSynchronizerBundle::class => ['all' => true],
or, on a legacy Symfony 2/3 app, wire it in app/AppKernel.php. If you are not on Symfony Flex, expect to enable it by hand.
Read these first (in order) — there is still no meaningful README, so the source is the documentation:
composer.json of the bundle — the ruwork/synchronizer constraint is the single most informative line in the package. It tells you which library API the bundle assumes.RuworkSynchronizerBundle.php — tells you what it registers.DependencyInjection/ (*Extension.php, Configuration.php) — the config tree, which is the contract you actually code against.Resources/config/ — service ids, tags, and whether anything is public.Synchronizer, SynchronizerInterface, plus any handler/comparator/resolver interfaces) — this release note is a compatibility bump, not a feature release, so the API surface you program against lives in the library, not the bundle. Read that package's 0.2 changelog for 0.1 -> 0.2 differences.In a running app, list the services it registers:
bin/console debug:container Synchronizer --show-private
bin/console debug:container --tag=ruwork.synchronizer # tag name from the compiler pass
bin/console debug:config ruwork_synchronizer # key from Configuration.php
First use case — the "diff two collections and apply/report the difference" problem: take a source iterable of domain objects and a target collection, compute adds/updates/removes, and apply them. Start with a one-off console command as your smoke test:
// src/Command/SyncCommand.php
protected function execute(InputInterface $in, OutputInterface $out): int
{
$syncer = $this->getContainer()->get(SomeSynchronizer::class);
$result = $syncer->synchronize($source, $target); // or ->sync(...)
$out->writeln(sprintf('+%d ~%d -%d',
count($result->getAdded() ?? []),
count($result->getUpdated() ?? []),
count($result->getRemoved() ?? []),
));
return 0;
}
Verify the actual method name against the 0.2.x interface — do not trust the above signatures; they are the shape, not the API, and 0.2 may have renamed things relative to 0.1. Once the command runs against a staging DB, you have the mental model needed for everything else.
Wrap, don’t scatter. Do not call the synchronizer directly from controllers. Put one thin application service per synchronizable relationship and inject the library's synchronizer into it:
final class OrderLineSynchronizer
{
public function __construct(private readonly SynchronizerInterface $synchronizer) {}
public function sync(Order $order, iterable $incoming): SyncReport
{
// map DTOs -> entities, then hand both sides to the package
}
}
This isolates the bundle/library behind your own interface, which matters enormously here: the 0.2.0 release proves the package can move, but a single tag on a previously dormant project is not a support commitment. Swapping it out later becomes a one-file change.
Type-hint the library, not the bundle. Since 0.2.0 exists purely to bind the two together, nothing in your application code should reference Ruwork\SynchronizerBundle\*. Constructor-inject the library interfaces and let the bundle's config supply the wiring. That also means your unit tests need no Symfony kernel.
Console-command workflow. Drive syncs from commands rather than request cycles. Typical flow:
--dry-run flag that runs the diff and prints adds/updates/removes without persisting (implement the dry-run in your wrapper, not the bundle).--limit/--batch for large collections; flush inside the loop if the underlying collection is Doctrine-managed.Doctrine integration. The natural fit is an explicit service call after forms/imports mutate a collection, then let the synchronizer reconcile the owning side. Prefer explicit calls in your service layer over lifecycle listeners: preUpdate/postFlush ordering around collection diffs is exactly where this kind of code becomes hard to debug.
Reporting pattern. If the bundle returns a result object, map it immediately into your own DTO/event (SyncReport, ItemAdded, ItemRemoved). This decouples logging, metrics, and messaging from whatever shape the package uses — and shields you from any 0.2 result-object renames.
Tag-based registration. If the bundle registers synchronizers by tag, use the tag for autoconfiguration:
services:
_defaults:
autoconfigure: true
App\Sync\:
resource: '../src/Sync/'
Check Configuration.php for the exact tag and required attributes before relying on this. A compatibility-only release is a good moment to re-read it — a tag name that changed in 0.2.0 will fail silently (services simply not registered).
Batch/console composition. For scheduled syncs, register the command in App\Command\ and schedule via cron or a scheduler; the bundle itself has no scheduling opinions.
"repositories": [{"type": "vcs", "url": "https://github.com/yourfork/..."}].^0.1 from another package, or a lockfile you forgot to refresh will boot fine and misbehave at runtime. After upgrading, composer show ruwork/synchronizer and confirm 0.2.x before debugging anything else.ruwork/synchronizer's upgrade notes for the 0.1 -> 0.2 diff and diff your own wrapper's calls against the 0.2 interface. Method/result renames on the synchronizer are the expected breakage.Bundle class signature changes — the 0.2.0 tag did not necessarily widen this. Test bin/console cache:clear before committing to it.debug:container and debug:config (with the bundle’s config key) are your only discovery tools. Do not guess config keys — read Configuration.php.$container->get() will fail. Inject via constructor in a service instead, or add a compiler-pass alias in your own bundle for the specific service you need.symfony/lock, custom syncers, doctrine/dbal sync tools). Namespace-qualify any service ids you define.synchronize twice and asserts the collection is unchanged the second time before you put it behind cron. Write it now, against 0.2.x, so a future bump has a regression net.debug level from your wrapper — the failure mode of collection reconciliation is usually an equality/identity comparison (=== vs. business key) that silently produces "remove everything, re-add everything." Force the synchronizer to compare on a stable business key rather than object identity. This is also the first thing to re-verify after the 0.2.0 upgrade.spl_object_id/business-key maps) if the bundle must go — and the 0.2.0 bump is a reminder to keep that interface narrow.How can I help you explore Laravel packages today?