Laravel - How to query a relationship on a pivot table - laravel

My Setup
I have a many-to-many relationship setup and working with my database. This is using a pivot table that has an extra column named "linked_by". The idea of this is that I can track the user that created the link between the other 2 tables.
Below is an attempted visual representation:
permissions -> permissions_roles -> roles
permissions_roles -> persons
The Issue
The permissions_roles table has an addition column names "linked_by" and I can use the ->pivot method to get the value of this column. The issue is that it only returns the exact column value. I have defined a foreign key constraint for this linked to persons.id but I can't manage to work out a way to query this from a laravel Eloquent model.
The Question
How do I query the name of the person linked to the "linked_by" column form the Eloquent query?
Ideally, I would like the query to be something like:
permissions::find(1)->roles->first()->pivot->linked_by->name;
BUT, as I haven't defined an eloquent relationship for this pivot table column I can't do this but I can't work out how I would do this if it is even possible?
Is the only way to do this to do:
$linkee = permissions::find(1)->roles->first()->pivot->linked_by;
$person = person::find($linkee);
$person->name;

->using();
I have discovered that Laravel has a way to do what I wanted out of the box by creating a model for the pivot table.
This works by adding ->using() to the return $this->belongsToMany() model function.
By putting the name of the newly created pivot model inside the ->using() method, we can then call any of the functions inside this pivot model just like any other eloquent call.
So assuming that my permissions belongs to many roles and the pivot table has a 3rd column named "linked_by" (which is a foreign key of a user in the Users table):
My permissions model would have:
public function roles()
{
return $this->belongsToMany('App\roles','permissions_roles','permissions_id','roles_id')
->using('App\link')
->withPivot('linked_by');
}
and the new link model would contain:
Notice the extends pivot and NOT model
use Illuminate\Database\Eloquent\Relations\Pivot;
class link extends Pivot
{
//
protected $table = 'permissions_roles';
public function linkedBy()
{
return $this->belongsTo('App\users', 'linked_by');
}
}
Obviously you would need to define the opposite side of the belongsToMany relationship in the roles model, but once this is done I can use the following to pull the name of the person that is linked to the first role in the linked_by column:
permissions::find(1)->roles->first()->pivot->linked_by->name;

You should be able to achieve that in toArray method in Permission model.
/**
* Convert the model instance to an array.
*
* #return array
*/
public function toArray(): array
{
$attributes = $this->attributesToArray();
$attributes = array_merge($attributes, $this->relationsToArray());
// Detect if there is a pivot value and return that as the default value
if (isset($attributes['pivot']['linked_by']) && is_int($attributes['pivot']['linked_by'])) {
$linkeeId = $attributes['pivot']['linked_by'];
$attributes['pivot']['linkee'] = Person::find($linkeeId)->toArray();
//unset($attributes['pivot']['linked_by']);
}
return $attributes;
}

Related

Laravel Eloquent Many to Many Relationship pivot table

I have three tables categories, film_categories and films and three models respectively Category, FilmCategory and Film.
I have seen many tutorials where they don't create a pivot table model like i have created FilmCategory Model . They just create a pivot table film_categories without a model.
My question is what is the best practise - ?
Should i create a FilmCategory model and set a hasMany relationship
class Film extends Model
{
use HasFactory;
protected $primaryKey = 'film_id';
/**
* Film film Relationship
*
* #return Illuminate\Database\Eloquent\Relations\HasMany
*/
public function categories()
{
return $this->hasMany(FilmCategory::class, 'film_id', 'film_id');
}
}
OR
Should i just create a pivot table film_categories without a model FilmCategory and set belongsToMany relationship
class Film extends Model
{
use HasFactory;
protected $primaryKey = 'film_id';
/**
* Film film Relationship
*
* #return Illuminate\Database\Eloquent\Relations\HasMany
*/
public function categoriesWithPivot()
{
return $this->belongsToMany(Category::class, 'film_categories', 'film_id', 'category_id');
}
}
Technically speaking, both option are fine. You can implement the two of them, and depends on what you want to implement/achieve in the logic part of the code, use one between the two that suits best. Here are things to consider:
First, depends on what is film_categories table used for. If the table is simply exists to relate films and categories tables using many-to-many relationship, then there's no need to create the FilmCategory model, and you can just use belongsToMany(). Otherwise, if the film_categories will relates to another table, then you should create FilmCategory model and define the relation using hasMany(). You're also likely need to add a primary-key field to film_categories if this is the case.
The second consideration to take is what kind of data structure you want to have. Using the codes that you provide, you can get Films using these 2 queries and it'll gives you the correct values but with different structures:
Film::with('categoriesWithPivot')->get();
// Each record will have `Film`, `Category`, and its pivot of `film_categories` table
// OR
// Assuming that `FilmCategory` model has `belongsTo` relation to `Category` ...
Film::with('categories', 'categories.category')->get();
// Will gives you the same content-values as the above, but with in a different structure
And that's it. The first point is mostly more important to consider than the second. But the choice is completely yours. I hope this helps.
Since the film_categories table does not represent an entity in your system, I think you should define a belongsToMany relationship and should not create a separate model for the pivot table. If You still wanna have a model for your pivot table so create a model that extends Illuminate\Database\Eloquent\Relations\Pivot class. Check documentation here: model for pivot table

Laravel BelongsTo relation - where on instance attribute

I have the following model:
class Order extends Model
{
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'shipping_email_address', 'email_address')
->where('customer_id', $this->customer_id);
}
}
Now when I call Order::with('user')->get(), it doesn't load the users.
I can access the user just fine when using Order::first()->user.
Is it possible to eager load a relationship with a where clause on a model instance attribute (like $this->customer_id)? Or is there another way to make a relationship based on two columns?
You can do this :
Your relation :
public function user()
{
return $this->belongsTo(User::class);
}
Then you can make query like this :
$userId = 5;
$result = Order::whereHas('user',function($q) use ($userId){
return $q->where('id',$userId);
});
Reply to your comment:
Having this relation :
public function user()
{
return $this->belongsTo(User::class);
}
Use this :
Order::with('user')->get()
This will retrieve all orders with its users. If you have some problem on that query then you have a wrong relationship. Make sure you have a foregin key in Orders table, if you dont espcify some foreign key on eloquent relationship, eloquent will understand than foreign key is : user_id, if not, especify putting more arguments to this function :
$this->belongsTo(User::class,...,...);
With function make join according to relationship configuration, just make sure the relation is ok. And all work fine !
If you want to keep your current flow, i would do it like so. Thou the josanangel solution is most optimal.
When getting orders include them using with. All these are now eager loaded.
$orders = Order::with('user');
Now utilize eloquent getters to filter the user by customer_id. This is not done in queries, as that would produce one query per attribute access.
public function getUserByCustomerAttribute() {
if ($this->user->customer_id === $this->customer_id) {
return $this->user;
}
return null;
}
Simply accessing the eloquent getter, would trigger your custom logic and make what you are trying to do possible.
$orders = Order::with('user')->get();
foreach ($orders as $order) {
$order->user_by_customer; // return user if customer id is same
}
Your wrong decleration of the relationship here is what is making this not function correctly.
From the laravel's documentation:
Eloquent determines the default foreign key name by examining the name of the relationship method and suffixing the method name with a _ followed by the name of the parent model's primary key column. So, in this example, Eloquent will assume the Post model's foreign key on the comments table is post_id.
in your case the problem is that laravel is searching for the User using user_id column so the correct way to declare the relation is
public function user()
{
return $this->belongsTo(User::class, 'customer_id'); // tell laravel to search the user using this column in the Order's table.
}
Everthing should work as intended after that.
Source: documentation

Laravel Pivot table id column

In Laravel and Eloquent you can use ManyToMany-Relations with a pivot table. The problem in my case is the automatic ID of the pivot row. Can I change how the ID is generated? I want to use UUIDs for that matter.
For other Objects you can just implement this behaviour in the Model, but there is no model for pivot objects.
Did I miss something?
Yeah, wherever you are adding new records to the pivot table, either through attach() or sync(), you give it a key/value array of additional items to put into the pivot table as the second parameter.
So for example $user->roles()->attach($role, array('id' => $uuid));
If you are doing this, it might be useful to also make sure your id isn't set to auto increment.
It might also be important to note that a lot of people don't even have an id column on their pivot table because it's usually not required unless you plan on creating a model for it, or it contains some other foreign key for some reason besides the 2 it would usually have.
I think creating Intermediate model does better job otherwise we will have to add primary uuid whenever we create new record.
use Webpatser\Uuid\Uuid;
use Illuminate\Database\Eloquent\Relations\Pivot;
class ProjectAssignee extends Pivot
{
public $incrementing = false;
protected $keyType = 'string';
protected $primaryKey = 'id';
protected $guarded = [];
public static function boot()
{
parent::boot();
self::creating(function ($model) {
$model->id = (string) Uuid::generate(4);
});
}
}
Now we can create attach easily
Project::first()->assignees()->attach(Assignee::inRandomOrder()->take(5)->pluck('id')->toArray());
Don't forget to chain using method to tell relationship about intermediate table.
public function assignees(){return $this->belongsToMany(
Assignee::class,
'projects_assignees',
'project_id',
'assignee_id')
->using(ProjectAssignee::class);}
Be careful here : id on a pivot table can cause confusion when doing a join, as it will end up on the same row as joined table's id, and can cause fairly nasty bugs, as the latter will override the former. If you really need one, at least change its name.

Laravel - Pivot table for three models - how to insert related models?

I have three models with Many to Many relationships: User, Activity, Product.
The tables look like id, name. And in the each model there are functions, for example, in User model:
public function activities()
{
return $this->belongsToMany('Activity');
}
public function products()
{
return $this->belongsToMany('Product');
}
The pivot table User_activity_product is:
id, user_id, activity_id, product_id. The goal is to get data like: User->activity->products.
Is it possible to organize such relations in this way? And how to update this pivot table?
First I suggest you rename the pivot table to activity_product_user so it complies with Eloquent naming convention what makes the life easier (and my example will use that name).
You need to define the relations like this:
// User model
public function activities()
{
return $this->belongsToMany('Activity', 'activity_product_user');
}
public function products()
{
return $this->belongsToMany('Product', 'activity_product_user');
}
Then you can fetch related models:
$user->activities; // collection of Activity models
$user->activities->find($id); // Activity model fetched from the collection
$user->activities()->find($id); // Activity model fetched from the db
$user->activities->find($id)->products; // collection of Product models related to given Activity
// but not necessarily related in any way to the User
$user->activities->find($id)->products()->wherePivot('user_id', $user->id)->get();
// collection of Product models related to both Activity and User
You can simplify working with such relation by setting up custom Pivot model, helper relation for the last line etc.
For attaching the easiest way should be passing the 3rd key as a parameter like this:
$user->activities()->attach($activityIdOrModel, ['product_id' => $productId]);
So it requires some additional code to make it perfect, but it's feasible.
The solution was found with some changes.
In the models relationships look like:
// User model
public function activities()
{
return $this->belongsToMany('Activity', 'user_activity_product', 'user_id', 'activity_id')->withPivot('product_id');
}
public function products()
{
return $this->belongsToMany('Product', 'user_activity_product', 'user_id', 'product_id')->withPivot('activity_id');
}
To update pivot table:
$user->products()->save($product, array('activity_id' => $activity->id));
- where product and activity ids I get from Input.
And, for example, to check if "user -> some activity -> some product is already exists":
if ($company->activities->find($activity_id)->products()->where('product_id', '=', $product_id)->wherePivot('company_id', $company_id)->get()->count() > 0) {
// code...
}
I think it needs improvements but it works for me now.

Where do i define the juncion table in many-to-many relationship in Laravel's Eloquent?

Here in the eloquent i see the Many to Many relationship:
http://laravel.com/docs/eloquent
I am not using Migrations and made two tables 'users' and a table 'roles' in phpmyadmin.
They both have a 'id' and 'name' column. Now i made the following models:
class User extends Eloquent {
public function roles()
{
return $this->belongsToMany('Role');
}
}
class Role extends Eloquent {
public function users()
{
return $this->belongsToMany('User');
}
}
My first question is. Do is still need to make junction table in phpmyadmin?
And if yes how do i tell Eloquent that (for example 'users_roles') is my junction table?
You must indeed have the table, and you can either follow Laravel conventions or specify it manually in the call to $this->belongsTomany();. Both ways are documented in the Eloquent documentation. The convention is in alphabetical order and singular table names (roles, users, role_user). To specify it in the call you can specify all sorts of things: return $this->belongsToMany('ForeignModel', 'pivot_table', 'local_id', 'foreign_id');
Yes, you still need to create the table.
In your models, add the table name as a second argument to belongsToMany.

Resources