bernskiold/laravel-record-merge
Bernskiold Laravel Record Merge helps you safely combine duplicate Eloquent models, moving related records and resolving conflicts so you can clean up your database without losing data or breaking relationships.
composer require bernskiold/laravel-record-merge and pin ^0.2.1 — earlier 0.2.x releases never dispatched the failure event, so any error-handling you build on top of it was effectively dead code.php artisan vendor:publish --provider="Bernskiold\RecordMerge\..."), then read the config file first. It's the fastest way to see which relations are movable, what the default merge strategy is, and which model contract/trait you need to add.Customer, Contact, Lead, Company). Only models carrying it are eligible sources/targets.Preview-then-commit. Resolve the merge service, run it in a "show me what would happen" mode (or diff relation counts yourself), present that to the user, then run the real merge inside a DB::transaction(). Deduplication is destructive and irreversible; never wire the commit straight to a single click.
Keep the controller thin, push the work to a job. For records with many relations (orders, notes, attachments, activity log), a synchronous merge will time out. Dispatch a queued job that owns the transaction and reports back via the package's events. The job also gives you a natural place to store a serialized "merge report" for auditing.
Listen to the merge lifecycle events. This is where the 0.2.1 fix matters. Register listeners for the success event (write an audit row, dispatch a "records merged" notification, clear caches, redirect the source URL) and the failure event (alert, log the exception, mark the job failed with context). Because the failure event previously never fired, treat 0.2.1 as the first version where your failed listener is actually exercised — write a test that forces a failure (e.g. a relation throwing, or a unique constraint violation) and assert the listener runs.
Centralise conflict resolution. Most merges need a policy for "both records have a value in this column." Wrap the package in your own small MergePolicy class or config array so that this rule lives in one place per model, rather than being reimplemented in every admin controller.
Dry-run in tests, assert on relations. A good regression test for your integration looks like: create target with 2 orders, source with 1 order and 3 notes, merge, assert target has 3 orders and 3 notes, assert source is gone (or soft-deleted), assert both events fired. Do this per model you enable merging for — relation mapping mistakes are the most common bug.
Idempotency guard. Before merging, verify the two records are different and that the target hasn't already absorbed the source (e.g. source still exists). Double-submitted forms are a realistic failure mode.
fail() → failed() rename (0.2.1) is technically a method rename on the event class. If you subclassed the event, called fail() yourself, or asserted on the old method name, update it. If you only registered a listener, nothing changes — but prior to 0.2.1 that listener was never invoked, so audit any "cleanup on failure" logic you assumed was running.withTrashed scoping — check how the package resolves relations and don't assume trashed children move.updated_at-driven caches) will run many times during one merge. Consider withoutEvents() around the merge if those side effects aren't wanted mid-operation.if ($model instanceof ...) branch scattered across your app.How can I help you explore Laravel packages today?