Laravel - Relationship between a table with two other tables - laravel

I have 3 tables: users, pools, permissions. Users and Pools have a pivot table since they have a many-to-many relationship.
Permission entries are created by me only manually. Then, each pool can assign permissions to their own users as they see fit. Therefore, the pivot table for linking users to permissions needs to have both pool_id and user_id and permission_id
So how does this pivot table work? How do I make a three-way pivot table?
EDIT: My question is basically asked here, with no satisfactory answer!

For pivot table linking 3 models you need additional data send when attaching models like in this answer: Laravel - Pivot table for three models - how to insert related models?:
$model->relation()->attach($otherModel, ['thirdModelKeyName' => 'thirdModelKeyValue']);
You can create custom relation class, say ThreeBelongsToMany, that will extend belongsToMany and override attach() method.
Then override belongsToMany() method on your models, that are involved in such relation (or use a Trait there) to return mentioned custom relation class instead of generic belongsToMany when applicable.
The attach() method could look like this:
public function attach($id, array $attributes = array(), $touch = true, $otherId = null)
{
if ( ! is_null($otherId))
{
if ($otherId instanceof Model) $otherId = $otherId->getKey();
$attributes[$this->getThirdKey()] = $otherId;
}
return parent::attach($id, $attributes, $touch);
}
Also you can use some helpers to fetch those related models, like for example (key names should be obtained with methods for flexibility, they are hard coded to make it short):
/**
* Accessor for far related model (thru pivot)
*
* #return mixed
*/
public function getTrackAttribute()
{
if (is_null($this->pivot)) return null;
if (is_null($this->pivot->track_id)) return null;
return ($this->pivot->track) ?: $this->pivot->track = Track::find($this->pivot->track_id);
}

Related

Invalid argument supplied for foreach() in laravel when use Eloquent: Relationships

I wanna get the class name of a user via the user ID. When I input the ID of a user so I will wanna get the class name. I have three tables such as users table, classes table, and class_users table. The class_users table is born from two users table and classes table.
A users table has an id, name, email, password.
A classes table has an id, class_code, class_name.
A class_users table has an id, class_id, user_id
And this problem relates to Eloquent Relationships.
Thank you for help.
My Route:
Route::get('/find_classes/{id}','StudentController#find_classes');
My Controller function:
public function find_classes($id)
{
$users = User::find($id);
foreach($users->classes as $class)
{
echo $class->name . '<br';
dd($class);
}
}
My User Model:
public function classes()
{
return $this->belongsTo('App\Classes','class_users','user_id','class_id');
}
Looks like you might have the wrong relationship set up on your User model. You have a one to many set up, but your DB is setup to handle a many to many. I suggest you change your User model relationship:
public function classes()
{
return $this->belongsToMany('App\Classes');
}
Note, you may need to name the FK on that relation, as I see you have class_id on the table, but your actual class is named 'Classes'. Check through your relationships to ensure this is explicit on the FK where it doesn't follow Laravel convention exactly.
With this relationship, your foreach loop should work. It would be a good idea for efficiency, as mare96 noted, to eager load the classes on the $users collection when you query:
$users = User::with('classes')->find($id);

Laravel pivot with multiple columns

Hi I have a problem with Laravel`s pivot table.
I have the following tables: students, courses and lessons.
The table lessons is connected with courses through a foreign key courses_id, and the tables students and courses are connected through a pivot courses_students.
So I can access the information through students like this:
//Students model
public function courses()
{
return $this->belongsToMany(Courses::class,'courses_students','student_id', 'course_id')
->with('lessons');
}
//Courses model
public function lessons()
{
return $this->hasMany(Lesson::class);
}
This works completely fine for this kind of relationship, but I want to add a third column in the pivot with name lesson_id for the lessons table.
I am doing this because, sometimes I need to get a specific set of lessons from each course for each user.
I succeeded in doing so, by using a model courseStudent for the pivot table.
Using the model for pivot my calls became like this.
Student->with('courseStudent.courses')
->with('courseStudent.lessons')
->get();
This partially does what I need it to do, but I want to maintain the relation ship between courses and students.
Is there a way to achieve that?
Example from docs(go through Many To Many):
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
Pivot table is meant to use belongsToMany relationship on both entities.
So your students and courses should have it defined if you want pivot table between that is using eloquent default capacity.
As a side note pay attention on naming convention because that way you will reduce issues on minimum: pivot table should be tableanamesingular_tablebnamesingular where order is set by alphabetical order of tables' names (i.e. post_user Yes, user_post No).
Id fields in pivot table should be tablenamesingular_id.
You can set names however you want but this way you will have less unepected behavior in future using eloquent. All of this you have in documentation page and I recommend you go through it thoroughly.
Other way is to use dynamic properties for getting certain values. Example from docs:
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
If you would like to manually change values in pivot table, you should create separate model for it that would be connected with that entity/table (pay attention that pivot model extends Pivot as in example from docs rather than Model):
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class PostUser extends Pivot
{
// other definitions related
}
You can use join for third relation:
public function courses(){
return $this->belongsToMany(Courses::class,'courses_students','student_id', 'course_id')
->withPivot('lesson_id')
->join('lessons','lesson_id','=','lessons.id')
->select('lessons.id','lessons.title', ...);
}
If you are going to use the same pivot table for courses and lessons, you can to do something like this:
//Students model
public function courses()
{
return $this->belongsToMany(Courses::class,'courses_students','student_id', 'course_id')
->whereNotNull('course_id');
}
public function lessons()
{
return $this->belongsToMany(Lessons::class,'courses_students','student_id', 'lesson_id')
->whereNotNull('lesson_id');
}
Then just use it:
$courses = $student->courses;
$lessons = $student->lessons;

Complicated Eloquent relationship using `Model::query`

I have a complicated relationship I'm trying to establish between two models.
The goal is to use $supplier->supply_orders to access the orders where the user supplies an item.
This throws: LogicException: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation.
With the code I've got I can use $supplier->supply_orders()->get(), however, when I try to use it as a relationship it throws. Since this is a relationship I should be able to wrap it in a relationship, but how would I go about doing that?
Supplier Model:
class Supplier extends Model {
public function supply_orders() {
return Order::query()
->select('order.*')
->join('item_order', 'order.id', '=', 'item_order.order_id')
->join('item', 'item_order.item_id', '=', 'item.id')
->where('item.supplier_id', '=', $this->id);
}
}
~~~ A whole lot of back info that I don't think you need but might ~~~
sql tables:
supplier
- id
items:
- id
- supplier_id
item_order:
- id
- order_id
- item_id
orders:
- id
The other Eloquent Models:
class Item extends Model {
public function orders() {
return $this->belongsToMany('Order');
}
}
class Order extends Model {}
Example of how this should work:
$supplier = factory(Supplier::class)->create();
$item = factory(Item::class)->create([
'supplier_id' => $supplier->id,
]);
$order = factory(Order::class)->create();
$order->items()->attach($item);
$orders = $supplier->supply_orders // Throws LogicException
This throws: LogicException: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
Sounds like a hasManyThrough with a many to many relationship. Laravel has no inbuilt support for this but you can always go ahead and write your own relationship like this: https://laravel.io/forum/03-04-2014-hasmanythrough-with-many-to-many
If you dont want relationships you can always do something like:
Order::whereHas('items.supplier', function($query) use($supplier) {
$query->where('id', $supplier->id);
});
For this to work, you need to have a relationship function items in your Order model and a relationship function supplier in your item model
I believe the reason it throws a relationship error is that you haven't created an Eloquent relation for
$supplier->supply_orders.
Instead, Laravel looks at your supply_orders() as a method in the class, and thus can't figure out which table to use as the pivot. To get the base relationship to work within Eloquent, you'd need to create a new pivot table for the relationship between suppliers and orders something like:
suppliers
-id
orders
-id
order_supplier
-id
-order_id
-supplier_id
From here, Laravel will accept a simple many to many relationship between the two (this would not cause a failure):
Supplier Class:
/**
* Get all orders associated with this supplier via order_supplier table
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function orders(){
return $this->belongsToMany("\App\Order");
}
Now that the relationship is solid both between the suppliers and orders, as well as the orders and items, you can eager load the relationship in all directions. Where it gets complicated for your particular need with the current DB setup is that you have a 3rd parameter from the items table that is not a direct pivot. Without having to re-structure the DB, I think the easiest would be to load your suppliers and the relationships like normal:
$suppliers = Supplier::with('orders', function($query) {
$query->with('items');
});
From here you've got all the relationships loaded and can draw down the ones with the right item->ids in a follow-up to the $suppliers collection. There are quite a few ways to skin the cat (even including all in one query) now that you have the Eloquent relationship... but I tend to keep it a little more simple by breaking it into a few readable bits.
Hope this helps.

Laravel: return multiple relationships

I have the following table:
The table is called user_eggs and it stores the user eggs.
eggs are items with additional data (hatch_time)
As you can see, user 2 has 2 eggs, which are items 46 and 47.
My items table stores the item general information such as name, image, description, etc...
How I can return the user eggs using $user->eggs() including the item data in my items table of the egg item_id?
I tried:
User Model:
/**
* Get the eggs
*/
public function eggs()
{
return $this->belongsToMany(Egg::Class, 'user_eggs','user_id','item_id')
->withPivot('id','hatch_time');
}
but $user->eggs() returns an empty array.
Any ideas?
A simple approach will be:
in your UserEgg model define:
/**
* Get the user associated with egg.
*/
public function _user()
{
return $this->belongsTo('App\User','user_id');
}
/**
* Get the item associated with egg.
*/
public function item()
{
return $this->belongsTo('App\Item','item_id');
}
then in your controller:
use the model to extract everything like this:
$userEggs = UserEgg::where('user_id',2)->get();
foreach($userEggs as $userEgg){
$associateduser = $userEgg->_user;
$associatedItem = $userEgg->item;
}
Short answer
If you loop through the user's eggs:
foreach($user->eggs as $egg){
$item = Item::find($egg->pivot->item_id);
}
If you want to query:
$user->eggs()->wherePivot('item_id', 1)->get();
Long answer
From the Laravel Documentation
Retrieving Intermediate Table Columns
As you have already learned, working with many-to-many relations requires the presence of an intermediate table. Eloquent provides some very helpful ways of interacting with this table. For example, let's assume our User object has many Role objects that it is related to. After accessing this relationship, we may access the intermediate table using the pivot attribute on the models:
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
Notice that each Role model we retrieve is automatically assigned a pivot attribute. This attribute contains a model representing the intermediate table, and may be used like any other Eloquent model.
By default, only the model keys will be present on the pivot object. If your pivot table contains extra attributes, you must specify them when defining the relationship:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
If you want your pivot table to have automatically maintained created_at and updated_at timestamps, use the withTimestamps method on the relationship definition:
return $this->belongsToMany('App\Role')->withTimestamps();
Filtering Relationships Via Intermediate Table Columns
You can also filter the results returned by belongsToMany using the wherePivot and wherePivotIn methods when defining the relationship:
return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);

Get pivot from two model instance

I have a question. Activity model and User model have many-to-many relationships. I know I can get pivot model like this:
$activities = $user->activities;
foreach($activities as $activity)
{
$pivot = $activity->pivot;
}
But now I already have two model instance:$user and $activity .Get them by id individually. Not access from their relationship. So I want to know is there a method to get their pivot model?
Its not necessary for you to call the relationship chain to access pivot, you can query individual model and access pivot too,
$model = Activity::find(1); //get the activity model by primary key
$model->pivot->activityId;

Resources