Prevent recursion in a Laravel Eloquent's self relationship

Viewed 647

Consider a table of nested locations which has columns for the location_id, its name and a parent_id. This parent field lets you know that you're in a nested location.

Example setup:

Locations
-----------------------------------
location_id |   name   | parent_id
-----------------------------------
      1     |   Isle A  |   NULL
      2     | Column 23 |    1
      3     |  Shelf 2  |    2
      4     |   Box C   |    3

Let's say we have an item sitting in location_id=4. We now want to easily show the item's full location in a comma separated format. We expect to see:

Isle A, Column 23, Shelf 2, Box C

In order to achieve that, I have the following Laravel Model with a full_name() function which takes care of formatting and recursive calls:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Location extends Model
{
    protected $table = 'locations';
    protected $primaryKey = 'location_id';

    protected $with = [
        'location'
    ];

    public function location()
    {
        return $this->hasMany('App\Location', 'parent_id', 'location_id');
    }

    public function full_name()
    {
        return ($this->location ? $this->location->full_name() . ', ' : '') . $this->name;
    }
}

Nothing fancy soo far, works correctly and outputs what I need it to. Now imagine down the line, some other piece of code allows for a mistake and the following happens:

Locations
-----------------------------------
location_id |   name   | parent_id
-----------------------------------
      1     |   Isle A  |   NULL
      2     | Column 23 |    3
      3     |  Shelf 2  |    2
      4     |   Box C   |    3

Notice that location #2's parent_id changed to 3. This creates an infinite loop of joins between 2 and 3. Laravel will not detect this recurrence (after all we're telling it to keep joining) and the execution will eventually die. This is my problem.

A possible solution I thought about would be to keep an ongoing list of "visited" location_id's, and if we stumble upon a repeat, we terminate the execution. If this was a "normal" function, I recon something like this would work:

public function location($recurrence_guard = [])
{
    if(in_array($this->location_id, $recurrence_guard)) {
        return collect([]); // or null?
    }
    array_push($recurrence_guard, $this->location_id);

    return $this->hasOne('\App\Location(<somehow pass $recurrence_guard_in_here>)', 'location_id', 'parent_id');
}

Clearly this is nonsense and that's not how Laravel's relationships work. I'm looking for ideas on how to pass extra parameters into the relationships or for any better ideas on how to design this.

Thanks!

0 Answers
Related