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

Uxdm Laravel Package

jord-jd/uxdm

UXDM is a PHP data migration library for moving records between sources and destinations such as CSV, JSON, XML, PDO and Eloquent. Map fields, transform values, and run repeatable imports or exports through a simple, extensible API.

Deep Wiki
Context7

Getting Started

UXDM is a plain Composer library — there is no service provider, facade, config file, or Laravel integration. You composer require jord-jd/uxdm and drive it from a command, job, or test.

The mental model (learn this in 5 minutes):

Migrator (source)  ──migrate()──►  DataSet (Items)  ──destination->performMigration()──►  Destination (sink)
  • Migrators (JordJD\UXDM\Objects\Migrators\*): ArrayMigrator, CSVMigrator, JSONMigrator, PDOMigrator, EloquentMigrator, BulkCSVMigrator.
  • Destinations (...\Destinations\*): the same family minus the bulk reader.
  • Fields (...\Fields\*): Field (1:1 copy), FieldCallable (transform), FieldValue (constant). You declare these on the migrator with setFromFieldName() / setToFieldName().
  • DataSet / Item: the intermediate representation. One Item ≈ one row; each carries the toFieldName keys the destination writes.

First use case — legacy table → users table, in php artisan tinker or a command:

use JordJD\UXDM\Objects\Migrators\PDOMigrator;
use JordJD\UXDM\Objects\Destinations\PDODestination;
use JordJD\UXDM\Objects\Fields\Field;

$migrator = new PDOMigrator(DB::connection('legacy')->getPdo());
$migrator->setTableName('members');
$migrator->setFields([
    (new Field())->setFromFieldName('id')->setToFieldName('legacy_id'),
    (new Field())->setFromFieldName('email')->setToFieldName('email'),
]);

$dataSet = $migrator->migrate();          // pulls everything into memory

$destination = new PDODestination(DB::connection()->getPdo());
$destination->setTableName('users');
$destination->performMigration($dataSet);

Where to look first in the repo: src/Objects/Migrators (what sources you can read), src/Objects/Destinations (what sinks exist), then src/Objects/Fields — 90% of your day-to-day work is choosing migrator + destination and writing field maps.

Implementation Patterns

1. Config-driven field maps. Don't hardcode maps in the command; keep them in config/migration.php and translate to field objects so the same command migrates many tables.

// config/migration.php
'users' => [
    'source' => ['connection' => 'legacy', 'table' => 'members'],
    'fields' => [
        ['from' => 'id',    'to' => 'legacy_id'],
        ['from' => 'email', 'to' => 'email'],
        ['from' => 'name',  'to' => 'name', 'callable' => 'trim'],
        ['value' => now(),  'to' => 'created_at'],
    ],
],

2. Use FieldCallable for every shape change — date normalisation, slugging, password hashing, enum mapping, FK lookups:

$field = new FieldCallable();
$field->setFromFieldName('joined');
$field->setToFieldName('created_at');
$field->setCallable(fn ($value) => Carbon::parse($value, 'Europe/London')->utc());

Use FieldValue for constants (tenant id, created_at, email_verified_at) rather than post-processing rows.

3. Chunk large tables yourself. PDOMigrator::migrate() hydrates the whole table. For anything beyond a few tens of thousands of rows, loop with Laravel's chunkById() and a fresh ArrayMigrator per chunk — you keep the field-mapping layer while bounding memory:

DB::connection('legacy')->table('members')->orderBy('id')->chunkById(2000, function ($rows) use ($fields, $destination) {
    $migrator = new ArrayMigrator($rows->map(fn ($r) => (array) $r)->all());
    $migrator->setFields($fields);
    $destination->performMigration($migrator->migrate());
});

Prefer BulkCSVMigrator / BulkCSVDestination when the intermediate format is a file — they stream instead of buffering.

4. Wrap in a command with a progress bar + --dry-run, and dispatch per-table jobs from a parent command so one failure doesn't kill the run. A --dry-run that only prints $dataSet->count() and the first 5 items will catch most field-mapping errors before you touch production.

5. EloquentDestination when you want model behaviour (casts, mutators, observers on the destination side), PDODestination when you want raw throughput or are writing to a foreign connection. A common split: PDO for bulk backfills, Eloquent for small/relational-adjacent imports.

6. Idempotency pattern. UXDM destinations insert; they don't upsert. For re-runnable migrations, either truncate the target in a transaction first, or skip this layer and use Model::upsert() / insertOrIgnore() after a dry-run diff. Record legacy_id on the target so a second run can be reconciled.

7. Test with ArrayMigrator. No DB needed: feed arrays, assert on the resulting DataSet items, or wire an ArrayDestination and compare arrays. This is the fastest feedback loop and catches field-name typos.

8. Custom endpoints are first-class. Implement the Migrator or Destination interface when you need an API source (Shopify, HubSpot, an S3 JSON dump) or a upsert-style sink. Wrap an existing destination in a decorator if you only need logging or row-count assertions.

Gotchas and Tips

  • Memory is the default failure mode. Every non-bulk migrator materialises the full dataset plus an Item object per row with a field object per column — a 500k-row import can easily be 1GB+. Use ->chunkById() + ArrayMigrator, or the Bulk* classes. Also call DB::disableQueryLog() around the loop; Laravel's query log will happily eat the rest of your RAM.
  • No relations, no foreign keys, no ordering guarantees. UXDM is flat row-in/row-out. Migrate parents before children, resolve FKs yourself (preload an id => new_id map and use it inside a FieldCallable), and be aware the destination driver may not enforce FK order for you.
  • PDODestination builds wide inserts. Chunk sizes that felt fine at 2k rows can blow past MySQL's 65,535 placeholder limit or max_allowed_packet when rows have many columns. Halve the chunk size before you blame the library.
  • Eloquent destinations fire events and timestamps. Observers, saving hooks, and updated_at churn will silently change your data or slow the import drastically. Use Model::withoutEvents() / saveQuietly() semantics and set timestamps explicitly via FieldValue.
  • Missing fromFieldName fails quietly. If the source column doesn't exist or is misspelled, you generally get null, not an exception — the row still inserts. Assert non-null counts on critical fields and add a --dry-run diff, or you'll discover empty emails three weeks later.
  • Everything from CSV/JSON is a string. Booleans ("1", "true", ""), decimals with commas, dates in the source system's locale — normalise inside FieldCallable and cast deliberately; don't rely on the destination column type to coerce.
  • CSV specifics: set the skip-header flag, watch for a UTF-8 BOM corrupting the first column name, and pass explicit delimiter/enclosure if the export came from Excel in a non-English locale.
  • Namespacing quirk: the package is jord-jd/uxdm but the PHP namespace is JordJD\UXDM (hyphens aren't legal in namespace segments). If IDE autocomplete lets you down, type the use statement by hand.
  • Pin the major version. The v1 → v2 line renamed and restructured several classes/methods; snippets from blog posts and older READMEs will not compile. Check composer show jord-jd/uxdm against the tags before copying any example.
  • Licence: LGPL-3.0. Fine to depend on in a closed-source Laravel app (you're linking, not modifying), but if legal review is in your path, flag it — and if you patch the library itself, you must publish those changes.
  • Debugging: dump at the DataSet level, not the destination. $dataSet->getItems() before performMigration() tells you whether the bug is in your source/mapping or in the sink. Nine times out of ten it's a field name or a null from an unmapped column.
  • Extend rather than fork. The interfaces are small; a custom Destination that wraps PDODestination and does upsert/logging/validation is twenty lines and keeps you upgradable.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package