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

Syncbundle Laravel Package

kmj/syncbundle

kmj/syncbundle is a PHP package that adds synchronization tooling to your application, helping keep local data in step with remote or external sources through a configurable, framework-friendly workflow. Install it with Composer and wire it into your existing setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before anything else. kmj/syncbundle is a Symfony 2.x bundle — PSR-0 autoloading, AppKernel registration, a DependencyInjection/ extension, and Symfony console commands. It is not a Laravel package despite the "Laravel-native" framing in the description. With 0 stars, 0 dependents, and no release history, composer require kmj/syncbundle will not only fail to install in a Laravel 10/11 app (Symfony 2.x http-kernel constraints conflict with ^6/^7), it will leave your composer.json pointing at abandoned code that touches production data. Do not attempt a native install.

The productive framing: read the repository as a reference design for prod→dev synchronization, then build the equivalent as two Artisan commands. Budget half a day for the read, one to two days for the rebuild.

Where to look first:

Path What you're looking for
composer.json Symfony/PHP constraints; confirms it's un-installable in a modern Laravel app
Command/ The real logic — likely mysqldump/rsync/ssh wrappers exposed as Symfony console commands
DependencyInjection/*Extension.php Config tree: source/target hosts, credentials, paths, exclude lists
Resources/config/*.yml Service wiring and default parameters — this is the "config surface" to replicate
README.md Intended workflow: pull vs. push, dry-run flags, any safety assertions

The first use case you actually care about is the one every Laravel team hand-rolls: "give me a production-shaped database and the uploads folder on my laptop, without leaking customer PII." In Laravel that's not a bundle — it's two guarded commands:

php artisan db:pull --anonymize --exclude=sessions,jobs,telescope_entries
php artisan storage:pull --disk=public

If you ever need to run the original bundle to compare behavior, do it inside a throwaway Symfony2 container with a read-only replica, never pointed at live production, and never from a developer machine.

Implementation Patterns

1. Config-driven source/target definitions

Mirror the bundle's DI parameters with a dedicated config file rather than scattering hostnames across commands. This is the one thing the bundle gets right that is worth copying directly.

// config/sync.php
return [
    'source' => [
        'connection' => 'prod_readonly',        // separate DB connection, read-only creds
        'ssh'        => env('SYNC_SSH_HOST'),
    ],
    'target' => [
        'connection' => config('database.default'),
    ],
    'exclude_tables' => ['telescope_entries', 'jobs', 'failed_jobs', 'sessions', 'cache'],
    'anonymize' => [
        'users'   => ['email' => 'safeEmail', 'name' => 'name', 'phone' => 'phoneNumber'],
        'orders'  => ['address' => 'address', 'notes' => 'sentence'],
    ],
];

2. Environment guards as a first-class concern

The bundle's most dangerous failure mode is bidirectional sync — a direction/config error that writes to production. Every command that can touch a database should assert on target before it opens a connection:

protected function assertTargetIsSafe(): void
{
    abort_if(app()->environment('production'), 403, 'Refusing to sync into production.');
    abort_if(config('database.default') === 'prod_readonly', 403, 'Target is the source.');
}

The bundle has no evidence of this. Add it first, not last.

3. Pull → transform → load, as three testable pieces

The bundle wraps the whole flow in one command. Split it instead so each stage is unit-testable:

  • Snapshot — mysqldump --single-transaction --no-tablespaces to a temp file (pipe through gzip for size).
  • Transform — Faker-based column masking driven by the anonymize config, using the model's $table metadata to map columns to faker providers.
  • Load — restore into the local connection with --no-create-info after a schema migration, or migrate:fresh + seed if the dataset is small.

Each stage takes a file path and returns a file path; only the command orchestrates. That mirrors the bundle's intent while giving you the "test-friendly, injectable components" the description claims but the Symfony2 wiring makes awkward.

4. Queue long stages, but never the whole command

If your dataset is large, dispatch the snapshot+transfer to a queue worker on a bastion host and have the command poll for completion. Do not queue the entire pull — the destructive restore step should stay synchronous and interactive, behind a confirmation prompt, so a misfire in CI can't wipe a developer's local DB unexpectedly.

5. Storage mirroring with the same shape

For uploads the bundle almost certainly shells out to rsync. Laravel's filesystem layer covers the pull side idiomatically:

Storage::disk('prod_s3')->allFiles($prefix);           // discover
foreach ($files as $f) { Storage::disk('local')->put($f, Storage::disk('prod_s3')->readStream($f)); }

For anything beyond a few GB, shell out to rclone sync or aws s3 sync and treat that as the snapshot stage — the pattern stays the same.

6. Idempotence and partial-failure handling

The command should be safe to re-run. Restore into a scratch database name (dev_restore_tmp), run post-restore migrations, then atomically rename/swap. If any stage fails, the previous local DB is untouched. The bundle relies on full dump+overwrite, which is not idempotent from the developer's point of view.

Gotchas and Tips

  • Do not put it in composer.json. Even in a require-dev block, composer update will attempt resolution, and its Symfony 2 constraints will either fail the build or drag in EOL transitive dependencies (security notifications will nag forever). Fork to an internal packages/ path if you truly want a pinned reference to read from — but you don't need it installed to read it.

  • PSR-0 vs. PSR-4. Symfony2-era bundles autoload via PSR-0 (underscore-in-classname directory mapping). If you copy any file verbatim, the namespace and file path will disagree by Laravel's PSR-4 rules and you'll get Class not found at the most confusing moment. Rename and re-namespace on copy.

  • config/database.php connection naming. The bundle likely has a single "prod" and "dev" host pair. Laravel's multi-connection setup is subtler: database.default changes what DB::table(...) points at, but Eloquent models with $connection set will ignore it. Verify every model or you'll mask data in the wrong database.

  • mysqldump version skew is the #1 restore failure. A dump produced by MySQL 8.x will fail to import into a developer's MySQL 5.7 with Unknown collation: 'utf8mb4_0900_ai_ci'. Add --compatible flags or, cleaner, force --default-character-set=utf8mb4 --skip-set-charset during snapshot. This is a real gotcha the bundle will not have solved for current MySQL versions.

  • Foreign keys block restores. SET FOREIGN_KEY_CHECKS=0 around the load, or you'll get half-applied imports on partial subsets. If you exclude a parent table (e.g. tenants) but keep a child (users), you'll hit constraint errors — the exclude list and anonymize list need to be reasoned about together, not independently.

  • PII anonymization is not free. Faker's safeEmail is fine for a users.email column, but if a real column holds JSON (Stripe payloads, addresses, support transcripts) and you don't mask it, you've copied PII into a lower environment. Enumerate JSON/text columns explicitly; don't default to "leave alone." The bundle predates GDPR-era scrutiny and almost certainly copies raw rows.

  • No tests, no CI, no release tags. There is nothing to verify against. Any behavior you assume ("does it use --single-transaction? does it drop the target first?") must be confirmed by reading the source, not inferred. Treat every line as undocumented.

  • Extension points are Symfony-shaped. If you were to fork, the natural extension points (CompilerPass, tagged services, EventDispatcher events) don't map to Laravel's Event::listen / AppServiceProvider::boot. Porting requires rewriting the wiring, not the logic — the logic is the small part.

  • Debugging a sync failure is a set -x exercise, not a Laravel one. The bundle shells out to ssh/mysqldump/rsync; when it fails you get an exit code and stderr. Wrap each shell call in Process::run() with captured output and log the full command (redacting credentials) — that's the debugging story you actually want, and it's 15 lines of code, not a dependency.

  • Where the pattern is genuinely useful. If your team is on Symfony 5.4 and needs this shape, look at dama/doctrine-test-bundle for DB isolation and write a db:snapshot console command against the DI you already have. If you're on Laravel, spatie/laravel-db-snapshots + a masking step covers 90% of this in maintained code.

Bottom line for a developer: spend twenty minutes reading Command/ and Resources/config/, extract the config schema and the pull/transform/load decomposition, and implement it as two guarded Artisan commands. The package is a worked example of an idea, not a tool you run.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core