nti/sync-bundle
NTI Sync Bundle provides configurable synchronization workflows for PHP applications, helping keep data consistent across services and systems. It reduces custom sync code with reusable operations, integration hooks, and straightforward setup for Symfony projects.
composer require nti/sync-bundle then add NTI\Bundle\SyncBundle\NTISyncBundle (verify the exact namespace in src/) to config/bundles.php. On Symfony Flex the recipe may not auto-register given the package's age — expect to do it by hand.README.md, then DependencyInjection/Configuration.php, then the Resources/config/services.*.yaml files. For a low-star bundle the config tree is the real documentation: it tells you every knob and default the maintainer actually supports. The service definitions tell you which class names are public and therefore intended for injection.composer.json require block against your PHP and Symfony versions. A 2023-07 release is likely targeting Symfony 5.4/6.x — if you're on Symfony 7, run composer why-not and be prepared to fork.Configuration tree have no Lumen/Laravel equivalent. Either run it inside a Symfony service and expose it over HTTP/queue, or treat the repo as a reference implementation and port the sync logic.Inject the manager, don't extend it. The bundle almost certainly exposes a central sync/manager service. Autowire that interface into your own application service and keep all your business rules outside the bundle. This is the single most important pattern here: with 0 dependents and thin docs, you want your sync logic to survive the day you fork or replace the package.
Command-driven sync as the default workflow. Wrap a sync run in a Symfony\Component\Console\Command. You get logging, exit codes, --dry-run, --since, and scheduler integration (symfony/scheduler or cron) for free. Structure:
#[AsCommand(name: 'app:sync:products')]
class SyncProductsCommand extends Command
{
public function __construct(private ProductSynchronizer $sync) { parent::__construct(); }
protected function execute(InputInterface $in, OutputInterface $out): int
{
$report = $this->sync->run($in->getOption('since'), $in->getOption('dry-run'));
$out->writeln(sprintf('created=%d updated=%d skipped=%d failed=%d',
$report->created, $report->updated, $report->skipped, $report->failed));
return $report->failed > 0 ? Command::FAILURE : Command::SUCCESS;
}
}
Make every sync idempotent and resumable. Persist a cursor (last synced ID/timestamp/checksum) in your own table, not in the bundle. A sync package's job is transport and mapping; bookkeeping is yours. This lets you re-run after a failure without duplicating rows on the far side.
Batch and stage. For anything above a few thousand records, sync into a staging table, validate, then swap or upsert. The bundle will not manage transactions across two systems — you will. Keep batches small (100–500) and commit per batch so a mid-run failure leaves a resumable state.
Wire it into Messenger for throughput. If the bundle exposes per-item sync services, dispatch one message per record and let symfony/messenger handle retries and rate limiting. This is usually a better fit than one long command, and it gives you a dead-letter queue for free.
Map via adapters, not conditionals. Write a small SourceRecord → DestinationRecord mapper class per entity type, and register it in your own registry. When the bundle's built-in mapping doesn't cover a case (it will not), you swap the adapter rather than patching vendor code.
Observe from day one. Route the bundle's channel into your normal logging and add a metric per run (count, duration, failure rate). With a package this lightly used, you are the QA team — you need numbers before you need features.
composer.json, commit composer.lock, and read the diff before every patch upgrade. Budget for the possibility of maintaining a fork.require constraints, missing Symfony 7 attributes, and PHP 8.3+ deprecations. Check its CI matrix to see which PHP/Symfony combinations were ever actually tested.nti/sync-bundle but the description says NTISyncBundle; the config alias is likely nti_sync but confirm in DependencyInjection/Extension.php. Getting the alias wrong produces a confusing "unrecognized option" error that looks like a bundle-registration failure.services.yaml alias to the public service ID rather than reaching into the container — the latter breaks on the next minor version.-vvv, temporarily set the bundle's log channel to debug, and dump the mapper input/output for a single record. Most "sync bugs" are field-mapping or null-handling bugs, not transport bugs. Isolate with one record before scaling.null vs unset is the classic sync failure mode: three distinct meanings in most source systems and one in most destination systems. Decide the policy once, encode it in the mapper, and write a test for each case.How can I help you explore Laravel packages today?