Photo by Biegun Wschodni on Unsplash

I the previous articles (Part 1 and Part 2) we’ve introduced a few strategies to merge plain Eloquent Models in Laravel. We have also seen how to add some validation constraints before merging what actually needs to be kept as different model instances.

In this last Part we will cover some examples on how to deal with relationship contraints and child models transfers.

Preserving the belongs to ownership

Imagine we have sheeps that belong to shepherds, and we are willing to merge those dangling Dolly clones, but make sure that they belong to the same shepherd (otherwise they might just share the same name/attributes but are not actual dupes).

$shepherd = DummyContact::create(['firstname'  => 'John',
'lastname' => 'Doe',]);
$sheepBaseDolly = DummySheep::make(['name' => 'Dolly', 'color' => 'white']);
$sheepDupeDolly = DummySheep::make(['name' => 'Dolly', 'color' => 'white']);
$shepherd->sheeps()->save($sheepBaseDolly);
$shepherd->sheeps()->save($sheepDupeDolly);

In order to validate that they belong to the same owner, we could easily do like so:

if ($sheepBaseDolly->owner->id != $sheepDupeDolly->owner->id) {
// ... looks like they actually belong to different parents, NO DUPE!
}

Of course, we may wrap this into a clean declaration in our package

try {$mergedModel = ModelMerge::mustBelongToSame('owner')
->setBase($sheepBaseDolly)
->setDupe($sheepDupeDolly)
->unifyOnBase();

} catch (ModelsBelongToDivergedParentsException $e) {

// ... NO DUPE !
}

This way, if it happen to be that we picked two different models belonging to different parents, that look the same, but should be kept separated, we have a safety guard here.

Transferring hasMany child models

Now let’s change the scenario. Instead of dividing the seas, we divided Moises the sheperd.

$moisesBase = DummyContact::create(['firstname'  => 'Moises',
'lastname' => 'Doe',
'age' => 120,
'phone' => '+1 123 456 789',
'created_at' => Carbon::now(), ]);
$moisesDupe = DummyContact::create(['firstname' => 'Moises',
'lastname' => 'Doe',
'age' => 120,
'created_at' => Carbon::now()->addDay(), ]);
$sheepDolly = DummySheep::make(['name' => 'Dolly', 'color' => 'white']);
$sheepMolly = DummySheep::make(['name' => 'Molly', 'color' => 'gray']);
$sheepRoberta = DummySheep::make(['name' => 'Roberta', 'color' => 'black']);
$moisesDupe->sheeps()->save($sheepDolly);
$moisesDupe->sheeps()->save($sheepMolly);
$moisesDupe->sheeps()->save($sheepRoberta);

So our goal is to get Moises merged back into one model, and also get all of the sheeps in one side; our merged version.

foreach ($moisesDupe->sheeps as $sheep) {
$moisesBase->sheeps()->save($sheep);
}

That was really easy. But as we can appreciate, it looks like we have hardcoded the relationship, and we can only move sheeps but no dogs. Let’s fix that.

protected $relationships = ['sheeps'];public function transferRelationships()
{
foreach ($this->relationships as $relationship) {
$this->transferChilds($relationship);
}
}
public function transferChilds($relationship)
{
foreach ($this->modelB->$relationship as $child) {
$this->modelA->$relationship()->save($child);
}
}

We can make use of this dynamic relationship spec from our package like this:

$moisesBase->sheeps()->count() // 0
$moisesDupe->sheeps()->count() // 3
$moisesBase = ModelMerge::withRelationships(['sheeps'])
->setBase($moisesBase)
->setDupe($moisesDupe)
->unifyOnBase();
$moisesBase->sheeps()->count() // 3
$moisesDupe->sheeps()->count() // 0

We’ve put Moises back in one piece and got all the sheeps in one pasture, all in one move.

What happens if we have many Moises dupes with many Dolly dupes and many dogs and parallel dupe universes?

Well, it looks like we really enjoy messing up our database. I think in that case it’s worth thinking of a methodic and efficient way of spotting dupes first. Let’s hope we can write on that in the near future.

Feel free to jump in to the package repo and suggest new scenarios to get covered in this article update.

--

--

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.