Self referencing "ownerless" many_many relationships? - laravel

I have a many many relationship where I don't care who created the relationship, if the querying models ID is in either side of the relationship I need the other side.
--
Let's say I have this model
class Ingredient extends Model
{
public function complements()
{
return $this->belongsToMany(
self::class, 'ingredient_complements',
'ingredient_id', 'complement_id'
);
}
}
And I've created a few different instances as below
$ing1 = new Ingredient();
$ing2 = new Ingredient();
$ing3 = new Ingredient();
$ing4 = new Ingredient();
$ing5 = new Ingredient();
$ing6 = new Ingredient();
$ing1->save(); //2,3,4,5,6...
Next I go through and relate a few of them
$ing1->complements()->attach($ing2);
$ing2->complements()->attach($ing3);
$ing3->complements()->attach($ing4);
$ing4->complements()->attach($ing6);
$ing5->complements()->attach($ing6);
$ing6->complements()->attach($ing1);
So now we have a pivot table that looks like
| ing_id | cmp_id |
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
| 5 | 6 |
| 6 | 1 |
So..
if I call $ing1->complements I'll get back ing2
if I call $ing2->complements I'll get back ing3
But..
if I call $ing2->complements I won't get back ing1
if I call $ing3->complements I won't get back ing2
I need to fix that.
I've got a second method that I'm using at the moment that I can call using $ing3->complementaryIngredients; which ends up executing something like
select * from `ingredients`
inner join `ingredient_complements` on `ingredient_id` = '3'
or `complement_id` = '3' and `ingredient_id` = `id`
or `complement_id` = `id` where `id` <> '3'
Which works, but I don't feel this is something that I should have to break out of the ORM for?
public function getComplementaryIngredientsAttribute()
{
return self::join('ingredient_complements', function(JoinClause $join){
$join->where('ingredient_id', $this->id);
$join->orWhere('complement_id', $this->id);
$join->on('ingredient_id', '=', 'id');
$join->orOn('complement_id', '=', 'id');
})->where('id', '<>', $this->id)->get();
}

You actually need to define bi-directional relationship, so your model should have 1 more method which will define the opposite direction.
public function ingredients()
{
return $this->belongsToMany(
self::class, 'ingredient_complements',
'complement_id', 'ingredient_id'
);
}
Then you will get the other side of your relation in this way $ing2->ingredients() and it will return what you are looking for.
Another approach is to create model for your pivot table so you can query that model to get both sides of your relationship

Related

In Laravel I'm attempting to get the info from a table with a pivot

I currently have 3 tables:
medias, colors, media_colors
media_colors is the pivot table I'm trying to use it contains
media_id | color_id
------------------
1 | 1
in colors table I have
id | front
----------
1 | color
My model for medias contains:
public function mediaColors()
{
return $this->belongsToMany(Color::class, 'media_colors');
}
In a controller I attempt to query front like so:
$m = Media::find($estimate->media_id);
dd($m->mediaColors[0]->pivot->front);
This returns null. How would I be able to access this?
You can access the colors directly from the relation collection as marked in the comment.
$m = Media::find($estimate->media_id);
foreach($m->mediaColors as $color) {
//you can access all the colors here
$fronts[] = $color->front;
}
//or if you only want the first one (if it exists)
$m->mediaColors[0]->front
one easier way to get just the first value if you only need that
$m = Media::find($estimate->media_id);
$front = $m->mediaColors()->value('front');
// this will return null if no relation found without triggering an exception

laravel Eloquent relationship using Distinct

I need show data with relationship using DISTINCT.
This is my sql data :
table pochettes
id |
49 |
table journees
id | formateurs_id | pochettes_id
1 | 3 | 49
2 | 4 | 49
3 | 3 | 49
table formateurs
id |
3 |
4 |
model Pochette
public function Journees()
{
return $this->hasMany('App\Journee','pochettes_id');
}
model Journee
public function Formateurs()
{
return $this->belongsTo('App\Formateur', 'formateurs_id');
}
I am used this code to show data with distinct formateurs because a Formateur can have many Journee in the same Pochette :
$pochette = Pochette::find(49);
foreach ($pochette->Journees->distinct('formateurs_id') as $formateur) {
echo $formateur->formateurs_id;
echo "<br>";
}
But this code dosn't work, I have this error message
**BadMethodCallException in Macroable.php line 81:
Method distinct does not exist.**
I would show this result :
3
4
not :
3
4
3
When you access an Eloquent relationship like a property, you will get an Eloquent Collection. However, distinct() is a Query Builder method and you need access to the Query Builder instance before returning the Collection. If you add () to the end of the relationship, you gain access to the Query Builder instance and can chain those Query Builder methods. In your case, you are actually looking for the groupBy() method and not distinct(). Don't forget to end the query with get().
foreach ($pochette->Journees()->groupBy('formateurs_id')->get() as $formateur) {
echo $formateur->id ;
echo "<br>";
}

Insert array on multiple rows

I have a multiple select:
Form::select('color', array('1' => 'Red', '2' => 'Blue', '3' => 'Green', ... ), null, array('multiple'));
How can I insert these values into a table on separate rows, like this:
id | user_id | color
----------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 4
5 | 2 | 1
6 | 2 | 3
In the above example, user with an id of 1 selected 4 different values in the select and each was inserted on a separate row.
I have this working in this way:
foreach (Input::get('tags') as $key => $value)
{
$user_color = new UserColor;
$user_color->user_id = $user->id;
$user_color->color = $key;
$user_color->save();
}
Is there a better way of doing this? It seems odd using a foreach loop when it feels like Laravel should have some sort of built-in method of inserting multiple values on multiple rows.
As Laravel doc provided,
You may also use the sync method to attach related models. The sync
method accepts an array of IDs to place on the pivot table. After this
operation is complete, only the IDs in the array will be on the
intermediate table for the model:
In this case,
$colors = Input::get('tags');
$user->colors()->sync($colors);
Please make sure to set relation in your User model :
public function colors()
{
return $this->belongsToMany('Color');
}
You can also use attach method when you parameter is not array. To more clear, Here is difference between attach and sync.

Laravel 4.2 HasMany Relationship using Like

I'm wondering if its possible to set the operator when using a HasMany relationship in Laravel 4.2.
I'm working with a users table and an email log table. The log table has a userID stored in serialised format (as there may be more than one userID stored within the log).
Users table
+---------+
| user_ID |
+---------+
| 1 |
| 2 |
+---------+
emailLog Table
+----+--------------------+
| ID | user_ID |
+----+--------------------+
| 1 | a:1:{i:0;s:1:"2";} |
| 2 | a:1:{i:0;s:1:"1";} |
+----+--------------------+
Am I able to use a hasMany relation with a 'Like' operator rather than an equals operator to return the correct email log ID? Would the statement be written something like the below?
return $this->hasMany('emailLog', 'user_ID', '%user_ID%', 'LIKE');
The proper way to return join table with a where clause would be:
return $this->hasMany('emailLog','user_id')->where('user_id', 'LIKE', '%user_id%');
I found a way to do something similar
class User extends Model
{
public function emailLogs()
{
// old code
// return $this->hasMany(EmailLogs::class, 'user_ID');
// Replace HasMany by like query
return EmailLogs::where('user_ID', 'LIKE', "%{$this->user_ID}%");
}
}

Laravel Eloquent Relationships for Siblings

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?
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.
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');
I think what you might want is this:
public function siblings() {
return $this->hasMany(self::class, 'parent_id', 'parent_id');
}

Resources