How to Create a Hierarchy in Laravel with Optional Cutoffs

Viewed 105

I am attempting to create a user hierarchy in Laravel 8, but have been bumping up against some issues.

Our use case is a very strange one based on the sales world. One user/sales rep may come in and begin working with us, then bring in their friend as a sub-user/sub-rep/“child” for them. The original “parent” user would want to see how their “child” is doing as far as sales go and such. Then that child user may bring in a few “children” users of their own over time. Sometimes, multiple parent users may come in as a group and do this, so the relationship seems like it should be many-to-many.

There are various one-off scenarios however where a child user may start their own sales company and while their personal sales may be viewed by the original parent user, anyone that they have recruited, they might only want to be viewed by the original child user and not the original parent user. This would be some sort of toggle able thing per user. It feels like a very involved use-case, so I’m not sure how to approach it fully.

Taking baby steps, my original idea was to create a many-to-many relationship with the users model having a user_user relationship table. Here is how it currently sits:

users table columns

id
Name

user_user table columns

parent_id
child_id

practice table columns

id
user_id (foreign id column to users table id)
name

user model

public function children() {
    return $this->belongsToMany(User::class, 'user_user', 'parent_id', 'child_id');
}

public function parents() {
    return $this->belongsToMany(User::class, 'user_user', 'child_id', 'parent_id');
}

public function practices() {
    return $this->hasMany(Practice::class);
}

practice model

public function user() {
    return $this->belongsTo(User::class);
}

Here is an example of a controller view attempting to find and combine sales locations/practices that a user at the top of a hierarchy would want to view for their entire structure.

// determine if viewing user has subreps
if (Auth::user()->children->count() == 0) {
    $practices = User::find(Auth::user()->id)->practices()->orderBy('name', 'asc')->get();
} else {
    // get the user's own practices
    $own_practices = User::find(Auth::user()->id)->practices()->orderBy('name', 'asc')->get();
    
    // create practices collection, then
    // merge the user's own practices into collection
    $practices = new Collection;
    $practices = $practices->merge($own_practices);

    foreach (Auth::user()->children as $child) {
        // get each child's practices, then
        // merge each practice into the practices collection
        $child_practices = User::find($child->id)->practices()->orderBy('name', 'asc')->get();
        $practices = $practices->merge($child_practices);
    }

    // sort the final practices collection alphabetically
    $practices = $practices->sortBy('name');
}

And here is an example of a hierarchy.blade.php unordered list I was trying to work on to see just how much work would be needed to dig into it all.

<ul>
    @foreach ($users as $user)
        @if ($user->children->count() == 0)
            <li>
                <a href="{{ route('users.show', $user->id) }}">
                    {{ $user->name }}
                </a>
            </li>
        @else
            <li>
                <a href="{{ route('users.show', $user->id) }}">
                    {{ $user->name }}
                </a>
                <ul>
                    @foreach ($user->children->sortBy('name') as $child)
                        <li>
                            <a href="{{ route('users.show', $child->id) }}">
                                {{ $child->name }}
                            </a>
                        </li>
                    @endforeach
                </ul>
            </li>
        @endif
    @endforeach
</ul>

As you can see, this works currently for a 1-deep level with some foreach looping, but I just can’t see a way to infinitely scale this in an efficient manner, much less have situations of being able to toggle cutoffs at different levels of viewership depending on user preference. I'd appreciate being pointed in a better direction for this hierarchal issue.

0 Answers
Related