Laravel 4 Polymorphic with multiple Pivot Tables - php

I'm trying to create a polymorphic relationship with multiple pivot tables. I have a table of requirements that can be assigned to accounts, roles, trips, and countries. This needs to be a many to many relationship because the same requirement could apply to multiple countries and/or trips and/or accounts etc.
I then need a table listing outstanding requirements for the user. For example: if a user has a certain account and there are requirements related to that account, then those requirements would be added to the user's list of requirements.
One solution I have is to first assign the requirements to the accounts, roles, trips, and countries using Pivot tables in a Many to Many relationship. Then using a polymorphic relationship I would connect the user to whichever pivot tables relate.
But I don't know how to do this or if it is even possible?
Here are my tables:
user_requirements
- id
- user_id
- requireable_id
- requireable_type
account_requirement
- id
- account_id
- requirement_id
role_requirement
- id
- role_id
- requirement_id
trip_requirement
- id
- account_id
- requirement_id
country_requirement
- id
- account_id
- requirement_id

Laravel 4.1 now has support for polymorphic many to many relationships.
Example below shows how I have implemented sharing Photos with both Products and Posts.
DB Schema
photos
id integer
filename string
alt string
photoable
id integer
photoable_id integer
photoable_type string
Models
Photo Model
class Photo extends Eloquent
{
public function products(){
return $this->morphedByMany('Product', 'photoable');
}
public function posts(){
return $this->morphedByMany('Post', 'photoable');
}
}
Product Model
class Product extends Eloquent
{
public function photos(){
return $this->morphToMany('Photo', 'photoable');
}
}
Post Model
class Post extends Eloquent
{
public function photos(){
return $this->morphToMany('Photo', 'photoable');
}
}
With the above, I can access all photos which are attached to a product as follows:
$product = Product::find($id);
$productPhotos = $product->photos()->all();
I can also iterate over to display all photos as any collection of models.
foreach ($productPhotos as $photo)
{
// Do stuff with $photo
}
The above can be replicated almost exactly to your requirements.
create a requirements table
create a requireable table
In Requirement model, declare all morphedByMany relationships
In Country, Trip, Role etc. declare morphToMany relationships
nb - I've typed this out freehand in S/O with no code editor, so there will probably be a typo, error or two - but concept remains the same.

A polymorphic relation in Laravel 4 is intended for single MODEL associations, therefore you cannot achieve what you are trying to build with this method. This is due to the fact that a pivot table doesn't represent a Model.

Related

No data fetched from associated table : Laravel

I have users and user_roles tables, and id of user_roles is used as foreign key in users. I fetched data from users using User::with('userRole')->find($user). In returned result userRole is present, however it is empty, instead it was supposed to have data from user_roles for the particular foreign key.
Please, share what can be the possible issues with the functionality or if anyone can explain working of laravel associations in brief.
/* User Model */
public function userRole()
{
return $this->belongsTo('App\UserRole');
}
/* UserRole Model */
public function user()
{
return $this->hasMany('App\User');
}
Thank You,
Many to many relationships in Laravel often require you to have 2 models and 3 tables. One to many relationships require you to have 2 models and 2 tables. One to one relationship also requires you to have 2 models and 2 tables.
Many to many relationship
Let's take User and Role models, since each User can have multiple roles and one Role can be assigned to different users, you will naturally want a Many to many relationship. Now, you will need to create an intermediate table in which you will store the results, why? Because you already defined both User and Role and since it is Many to many none of those objects will have any identifier of the other one inside of them, but rather will have their own identifier in the intermediate table, and this is how Laravel fetches the relationship, it connects Models primary key with foreign key inside of the intermediate table.
One to many relationships
Let's take User and Role models again and let's say that this time, one Role can be assigned to multiple users, but one User can ONLY have 1 Role. Naturally you will have a field role_id inside of your User model and you will connect role_id from users with id from roles.
One to one relationships
Lets take User and Role models again :D Let's say you want to create a separate Role for every user, then you will have 2 models and 2 tables where your users table will have all the users and your roles table will contain id, name, user_id and now when you try to retrieve the relationship, if you define one to one laravel will return only 1 result, no matter if you have multiple same user_id on the roles table, Laravel will return only 1 role because you told him explicitly it's 1-to-1 relationship.
EDIT:
Here is an example of one to one relationship:
/* User Model */
public function userRole()
{
return $this->belongsTo('App\UserRole', 'user_role_id', 'id');
}
#Tim and #Nikola
Thank you, for your efforts mates.
However, I found the reason behind the problem. It was because the wrong naming of userRole function in User model.
I was using foreign key of user_roles as user_roles_id in users table and defined function with name of userRole in User model. This leads to ORM not found the relevant column for attaching user_roles data.
The solution is either I have to change the name of user_roles_id to user_role_id or userRole function name to userRoles. And I choose the first one and it worked fine. :)
For reference on the naming conventions of laravel please refer to Laravel - Database, Table and Column Naming Conventions?.

L5.5 relationship of pivot table with another model

In my application, a model Device has a many-to-many relationship with model Task.
A combination of Device and Task could be assigned to a various number of model User.
example: Device A has a Task Check something and this should be done by User Frank and Steven.
From my point of view, this should be a "standard problem", but so far I could not find a proper solution.
Right now, I use following workaround:
a) added an unique ID id to the device_task pivot table
b) query id from the pivot table
c) create a new table device_task_user which contains user_id and device_task_id
b) use query builder to get/add users
But I am really not happy with this approche.
Would it be possible, that the pivot table also extends Model and then have a one-to-many relationship with User?
Or would you suggest to add a json colum to the pivot table and store the users there?
Any idea would be very welcome!
Would it be possible, that the pivot table also extends Model
Yes, it's possible. From the docs:
If you would like to define a custom model to represent the intermediate table of your relationship, you may call the using method when defining the relationship. All custom models used to represent intermediate tables of relationships must extend the Illuminate\Database\Eloquent\Relations\Pivot class
You also can create a new hasMany() and belongsTo() relationships between Task and Device models and use them as well as existing belongsToMany relationship. And you'll need to define a new relationship between pivot model and User model to be able to get data by device, task or user.
Modify many-to-many relationship to hold an extra field user_id
class Device extends Model
{
public function tasks()
{
return $this->belongsToMany(
Task::class,
'device_task',
'device_id',
'task_id'
)->withPivot('user_id');
}
}
And when updating do like this in controller
$device->tasks()->attach([$taskId]=>['user_id']=>$userId);
And of-course you need DeviceTask model and also a has-many relationship between User model and DeviceTask model to get user's task

Why the name convention of Laravel relationships so weird?

I have three tables:
users
columns: id, name
books
columns: id, name
book_user:
columns: user_id, book_id, state(not read yet, reading, read already)
I intended to user book_user as many-to-many relation table, so I follow the name convention from doc:
To define this relationship, three database tables are needed: users, roles, and role_user. The role_user table is derived from the alphabetical order of the related model names, and contains the user_id and role_id columns.
I wrote code:
class User extends Model
{
public function books()
{
return $this->belongsToMany('App\Book');
}
}
, and I can retrieve the books which related to the user by call user->books().
That works well, but when I try to retrieve the state of the book which related to a user, I create model:
class BookUser extends Model
{
//
}
When I use this Model, it claims:
Base table or view not found: 1146 Table 'myapp.book_users' doesn't exist
Conclusion:
the name convention of a table which can be used as many-to-many is <singular_noun>_<singular_noun> (such as book_user).
the name convention of table with multiple words which mapping to a Model is <singular_noun>_<plural_noun> (such as book_users).
I know I can set the table name manually which a model mappings to, but I just wonder:
Does that conflict is a design flaw or just I'm doing wrong in designing tables and models?
You don't need define a model for pivot table,just add withPivot
class User extends Model
{
public function books()
{
return $this->belongsToMany('App\Book')->withPivot('state');
}
}
Then you can retrieve book states from model pivot
Generally you don't need a model for pivot tables. You can define the inverse of the relationship. And if you want to store extra data in pivot table maybe you can check withPivot method. Or explain what are you trying to do.
But if you want to create a model, you need to specify your table name in your model manually. Because Laravel doesn't know if its a pivot table or normal table. It just tries to guess the table name by making it plural.

Is this many-to-many relationship possible in Laravel?

I'm trying to get my head around using polymorphic relationships for a many-to-many relationship between suppliers and products:
products
id
name
suppliers
id
name
product_supplier
id
product_id // belongsToMany easily takes care of this id
supplier_id // and this id
price // this can be fetched using withPivot('price')
deliverymethod_id // I'm having difficulties "joining" this one.
I'm confident in using belongsToMany(), I can easily do something like this:
public function products()
{
return $this
->belongsToMany('Supplier')
->withPivot('price');
}
But the catch here is joining to that third column in the relationship table:
deliverymethods
id
name
I am unsure how to do this. I've been told that Polymorphic Relationships are what I'm after however I'm unsure how to implement them for my situation.
http://laravel.com/docs/4.2/eloquent#many-to-many-polymorphic-relations
According to the documentation, I would have to rename my table columns to include *able_id and *able_type. This is really confusing.
I was expecting laravel to having something like belongsToMany('Supplier')->withAlso('Deliverymethod')
I'm afraid that method does not exist (yet?).
What I fall back to is manually filling in the 3rd relation:
public function products()
{
return $this
->belongsToMany('Supplier')
->withPivot('price', 'delivermethod_id');
}
Now I can access ->pivot->deliverymethod_id on every Product that I get via Supplier.
You could even add a function in your Product model that fills this in automatically:
Class Product ... {
protected $appends = array('deliverymethod');
public function getDeliverymethodAttribute()
{
return Deliverymethod::find($this->pivot->delivermethod_id);
}
Now every time you request a product via it's relation to the supplier, it automatically includes a deliverymethod attribute with the object in it.
(To have it not throw an error when you get a Product directly, just remove the $appends variable from the Product model and call the getDeliverymethodAttribute() method manually whenever you need it.)
Short explanation about polymorphic relations:
Polymorphic relations are for relations, where two models are related to a third model at the same time. So for example both a User and a Product can have a Picture of them. Now, it doesn't make sense to have two models for the pictures (UserPicture and ProductPicture), since they both have the same characteristics. This would be a perfect reason to use a polymorphic relation, where the Picture can both belong to a User or a Product.
However, in your case the Deliverymethod applies directly to the relation between Supplier and Product. So this is not where polymorphic relations would work, but it has instead to be done the way you did it.

Polymorphic many-to-many relation

I couldn't get related models in many-to-many polymorphic relation saved into database.
$photo = Photo::find(1);
$photo->articles()->attach(2);
something like this wouldn't work and gives
error: Call to undefined method
Illuminate\Database\Query\Builder::articles()'
How to do it the right way?
Models
class Tag extends Eloquent
{
public function articles()
{
return $this->morphedByMany('Article', 'taggable');
}
public function photos()
{
return $this->morphedByMany('Photo', 'taggable');
}
}
class Article extends Eloquent
{
public function tags()
{
return $this->morphToMany('Tag', 'taggable');
}
}
class Photo extends Eloquent
{
public function tags()
{
return $this->morphToMany('Tag', 'taggable');
}
}
Finally got it working... Basically it's polymorphism + many to many relationship combined. I thought it don't require tags table. Taggables table acts as pivot table and tags is the table which contains Tag objects that connect models based on pivot table (taggables table)
Morphing does not Support Many To Many, it is actually a one to one association, it is used to create relations between different Models, so for example if you have Tag Model, the tag model could relate to many different occasions, it could relate to Page Model, to Post Model, to Product Model etc, what it actually achieves is that it allows you to relate to that other model without explicity defining the related model in the DB level, for instance a Tag model could be associated with a Post Model, but then you would need to define in the db, a post_id foreign key, with Polymorphic relations you can relate to that Post Model, without defining the model on the DB level as a specific field:
Polymorphic relations allow a model to belong to more than one other model, on a single association
And in Django Documentation you can clearly see how this association works:
staff
id - integer
name - string
orders
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string
The association from above is: 1 Photo relates to 1 Other Model, but with a different concrete implementation* thus a photo can belong to Staff, or it can belong to an order.

Categories