Laravel Eloquent Relationships for Siblings

2019-08-29 10:59发布

问题:

Relationship for Siblings are many to many self relationship. So, for many to many self relationship, we can define two functions in the model:

public function siblings()
{
    return $this->belongsToMany('Student', 'student_sibling', 'student_id', 'sibling_id');
}

public function siblingOf()
{
    return $this->belongsToMany('Student', 'student_sibling', 'sibling_id', 'student_id');
}

The first one returns the students who are siblings of the student. The reverse is also true for siblings. So, the second one returns the students of whom the student is a sibling.

So, we can merge both the collections to get a list of students who are siblings of the student. Here is my code in the controller method:

    $siblingOf = $student->siblingOf;

    $siblings = $student->siblings;

    $siblings = $siblings->merge($siblingOf);

But there is more. Siblings relationship is a chain relationship unlike friends relationship. This mean, if X is a sibling of Y and Y is a sibling of Z, then Z is a sibling of X.

So, how to get the collection of all students who are sibling of a student?

回答1:

I'm assuming that you have a Student model, with student_id as your primary key, and a pivot table that looks something like this:

+---------+------------+------------+
| id (PK) | student_id | sibling_id |
+---------+------------+------------+
| 1       | 1          | 2          |
| 2       | 2          | 1          |
| 3       | 3          | 4          |
| 4       | 4          | 2          |
| 5       | 6          | 5          |
+---------+------------+------------+

In this example, we want to be able to discern that 1, 2, 3 and 4 are all siblings of each other. 5 and 6 are siblings as well.

One way to solve this is by gathering the result of the two belongsToMany() relationships in your model—as you did in your question—and then recursively iterating over the result, continuing to check the belongsToMany() functions until we run out of siblings.

In the Student model, define two functions:

public function getAllSiblings(){

    // create a collection containing just the first student
    $this->siblings = $this->newCollection()->make($this);

    // add siblings (recursively) to the collection
    $this->gatherSiblings($this->student_id);

    // OPTIONAL: remove the first student, if desired
    $this->siblings->shift();

    return($this->siblings);
}

private function gatherSiblings($student_id){

    $checkStudent = Student::find($student_id);

    if ($checkStudent) {
        // get related siblings from model, combine them into one collection
        $siblingOf = $checkStudent->siblingOf()->get();
        $siblings = $checkStudent->siblings()->get();
        $siblings = $siblings->merge($siblingOf);

        // iterate over the related siblings
        $siblings->each(function($sibling)
        {
            // if we've found a new sibling who's 
            // not already in the collection, add it
            if(!$this->siblings->contains($sibling->student_id)) {
                $this->siblings->push($sibling);

                // look for more siblings (recurse)
                $this->gatherSiblings($sibling->student_id);
            };
        });
        return;
    }
}

In your controller, find the initial student, and then call getAllSiblings() from Student:

$student = Student::find($id);
$siblings = $student->getAllSiblings();

The result is a collection with all the siblings of the original student. So, if you run this for student 1, you will get a collection containing students 2, 3 and 4. (If you'd prefer to keep the original student as part of the siblings collection, so that running this for 1 returns 1, 2, 3 and 4, simply remove the optional step in getAllSiblings().)

From there, you can cast the collection to an array, or sort, etc. as needed.



回答2:

Recursive relation could do this, BUT it will probably cause infinite loop (function nesting limit error).

Anyway this is how to setup such relation:

public function siblingsRecursive()
{
    return $this->siblings()->with('siblingsRecursive');
}

public function siblings()
{
    return $this->belongsToMany('Student', 'siblings');
}

Then you call, as simple as this:

$students = Student::with('siblingsRecursive');