Merging Eloquent Models in Laravel

Ariel Vallese
4 min readSep 10, 2018

--

Photo by Jørgen Håland on Unsplash

After having built a couple SaaS Laravel applications I eventually came across the situation of having duplicate model instances in my database.

In my case, these models are addressbook-like contacts (users, patients, etc), and could be generated by some of the users, either through a double-submit or simply posting again after not realizing it already existed in the database.

Of course, a good practice to avoid this would be directly preventing the dupes creation in first place. But sometimes, as your SaaS app evolves, or you migrated old data, the problem is already there and you just need to deal with it.

This brief article covers how we could have an automated and consistent way to merge two pure or seemingly duplicated Eloquent models in some way.

Let’s jump into an example. My realworld application (called Fimedi NET) handles a patients addressbook for doctors, and I sometimes see this situation happening:

  • Patient visits doctor for the first time. Doctor takes patient’s contact data and creates a new patient record.
  • Next year or so this patient visits the doctor again, and because either the doctor forgot to find the previous record first, or we did not yet implement this automated check or constraint, he take’s patient’s information again and created a new record with same or similar attributes.
  • Voilà. We now have a model dupe in our database.

Fixing the trivial case

Good enough, for the most trivial cases on which the attributes duplication is an exact match, the simplest way to fix is by simply deleting the newest (or oldest, at your discretion) model.

// Duplicated models example (Trivial Case)
$firstVisitPatient = Patient::make(['firstname' => 'John', 'lastname' => 'Doe']);
$secondVisitPatient = Patient::make(['firstname' => 'John', 'lastname' => 'Doe']);
// Simple fix for keeping one instance of John Doe profile
$secondVisitPatient->delete();

Fixing a mixed case (the quick and dirty way)

Now imagine that the doctor would take note of some new attributes this second time, and we would like to preserve as much registered attributes as we can:

// Duplicated models example (Mixed Case)
$firstVisitPatient = Patient::make(['firstname' => 'John', 'lastname' => 'Doe', 'age' => 33]);
$secondVisitPatient = Patient::make(['firstname' => 'John', 'lastname' => 'Doe', 'phone' => '+1 123 456 789', 'eyes' => 'green']);
// A possible fix would be to copy data from B to A and get rid of B
$firstVisitPatient->phone = $secondVisitPatient->phone;
$firstVisitPatient->eyes = $secondVisitPatient->eyes;
$secondVisitPatient->delete()
dd($firstVisitPatient->toArray());array:5 [
"firstname" => "John"
"lastname" => "Doe"
"age" => 33
"phone" => "+1 123 456 789"
"eyes" => "green"
]

But this could become cumbersome if we had more fields to take care of.

Using a simple merge strategy (a more Laravel way)

The structure of Eloquent models allow us to get their attributes as an array, merge them as an array, and re-fill our merge result.

$modelA = Patient::make(['firstname' => 'John', 'lastname' => 'Doe', 'age' => 33]);
$modelB = Patient::make(['firstname' => 'John', 'lastname' => 'Doe', 'phone' => '+1 123 456 789', 'eyes' => 'green']);
$dataA = $modelA->toArray();
$dataB = $modelB->toArray();
$dataMerge = array_merge($dataA, $dataB);$modelA->fill($dataMerge);dd($modelA->toArray());array:5 [
"firstname" => "John"
"lastname" => "Doe"
"age" => 33
"phone" => "+1 123 456 789"
"eyes" => "green"
]

Wrapping up

This solution was for me a fair MVP for most cases. Adding a few views and controllers would allow me to give doctors the ability to pick and perform their own merges.

But since there exist more complex scenarios (covered in Part 2), I decided to create a simple package that would handle most of the merging complexity and be flexible enough as to allow diverse merging strategies.

An example of how to simplify this use it goes like this:

$modelA = Patient::make(['firstname' => 'John', 'lastname' => 'Doe', 'age' => 33]);
$modelB = Patient::make(['firstname' => 'John', 'lastname' => 'Doe', 'phone' => '+1 123 456 789', 'eyes' => 'green']);
$mergedModel = ModelMerge::setModelA($modelA)->setModelB($modelB)->merge();dd($mergedModel->toArray());array:5 [
"firstname" => "John"
"lastname" => "Doe"
"age" => 33
"phone" => "+1 123 456 789"
"eyes" => "green"
]

Feel free to dive in the package and suggest enhancements:

At the time of writing this it has only implemented the simple array merge strategy. In PART 2 I’m covering a bit more complex scenarios.

Merging Models with Relationships

Wondering about relationships? I’m covering a bit of this in PART 3

Do you need help for customizing? Drop me a line.

Thanks for reading!

--

--

Ariel Vallese

Hi, I’m Ariel Vallese, I enjoy coding on Laravel. Created Fimedi NET for dietitians and patients and Timegrid in the past. Also love diving and traveling.