Should some relationship tables have their own models? - php

I'm writing an API with an MVC framework in PHP, and I'm using the Eloquent ORM.
My app has some models which are eventually linked through relationship tables, but intended to be created in a separate, decentralized manner.
Should these relationship tables have their own models, or should the models that are related have methods to create links?

With Eloquent, in regards to intermediate or pivot tables with many to many relationships, you shouldn't need to create an additional model.
You should always set up the relationships for related Models with the belongsToMany() method, documented here: https://laravel.com/docs/5.3/eloquent-relationships#many-to-many
class User extends Model
{
/**
* The roles that belong to the user.
*/
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
They have various methods of then using this relationship to adding or updating items to the pivot table including attach, detach, or sync documented here: https://laravel.com/docs/5.3/eloquent-relationships#updating-many-to-many-relationships
$user = App\User::find(1);
$user->roles()->attach($roleId);
You can also add data to extra fields:
$user->roles()->attach($roleId, ['expires' => $expires]);
Extra fields on the pivot table are important when you have data, like timestamps, that relate to the relationship and not either of the related models.
An example I could think of would be if you wanted to maintain a history of store managers. You wouldn't just want a store_id and manager_id, you'd also want a started_at and ended_at timestamp. This would allow you to view who is managing a store right now, but also who managed a store in the past.
With Eloquent, these types of extra fields don't require their own model, they can be accessed through the various methods documented above.

Related

Lumen - Eloquent: Override name of joining table

I'm building a DB for a software where authentication is coupled with the companys LDAP Server.
I now have the two tables
AD_Groups
and
AD_Users
Which are joined in the table
AD_UsersXAD_Groups
I already learnt about establishing relationships in eloquent.
The many to many relationship is exemplified in the official docs by this:
https://laravel.com/docs/5.8/eloquent-relationships#many-to-many
Now, as you can see, the following feature of eloquent won't help me much:
"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 therefore need to override this derived name by using the second parameter, as described here:
"As mentioned previously, to determine the table name of the relationship's joining table, Eloquent will join the two related model names in alphabetical order. However, you are free to override this convention. You may do so by passing a second argument to the belongsToMany method:
return $this->belongsToMany('App\Role', 'role_user');
But as seen in the above example from the docs, the infamous "snake case" is still applied to the name.
However, I'm affraid this might not work for my case.
Admittedly, AD_UsersXAD_Groups is pretty ugly, and I fear that eloquent/lumen will not be able to correctly identify its elements and apply the snake case rule correctly.
But I don't know for sure, and therefore I'm asking you what will be the most likely to work.
Using AD_UsersXAD_Groups or AD_UserXAD_Group
Because you have an "x", the Eloquent magic will never be able to match your table automatically.
You can override the table name in the relationship in your User model. You can also specify the keys if they are not Eloquent's expected "group_id" and "user_id":
function groups() {
return $this->belongsToMany(GroupModel::class, 'AD_UsersXAD_Groups', 'user_id_key', 'group_id_key')
}
And in your Group model you could do this to reverse it
function users() {
return $this->belongsToMany(UserModel::class, 'AD_UsersXAD_Groups', 'group_id_key', 'user_id_key')
}

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

Multiple tables with one to many relationship(Laravel)

I have 3 tables namely A, B and C.
All 3 of them need to make use of a notes table(i.e. A has many notes and a note belongs to A. The same follows for remaining 2 tables).
So I created 3 more tables: A_note, B_note and C_note(as mentioned here: multiple tables need one to many relationship).
Is there a way to do CRUD operations on these tables(A_note, B_note and C_note) similar to the way we handle pivot table in many-to-many relationship?
Please suggest a way forward.
You should be using polymorphic relationships for this. It allows multiple models to make use of a single table.
Have a look at the documentation here
In your particular case, each of your tables would reference a noteable_id column and a noteable_type.
The noteable_id will contain the id of the (A/B/C) model.
The noteable_type will contain the string name of the model (A/B/C).
The (A/B/C) models will now get a new attribute:
/**
* (A/B/C) Model(s)
*/
public function notes()
{
return $this->morphMany('App\Notes', 'noteable');
}
And the note model will initiate it's polymorphic properties against the attribute name used to identify your polymorphic ids and types:
/**
* Note Model
*/
public function noteable()
{
return $this->morphTo();
}
Now you can simply call ->noteable on the (A/B/C) models, and they all share 1 table without the need of another pivot table for each table.

get *related* linked models in laravel eloquent instead of raw SQL

I'm trying to get 'related' linked models by querying a link table, named company_projects which holds (as you expect) the id's of companies and projects (projects are kind of product-categories).
In this case, the used flow to determine a related project is:
Get companies who are in the same project ('product category') as you
Find the other project id's which are linked to those companies
Get the info of the linked projects fetched by last step
What i'm trying to do is already functional in the following raw query:
SELECT
*
FROM
projects
WHERE
projects.id IN
(
SELECT cp1.project_id
FROM company_projects cp1
WHERE cp1.company_id IN
(
SELECT cp1.company_id
FROM projects p
LEFT JOIN company_projects cp2 ON cp2.project_id = p.id
WHERE p.id = X AND cp2.company_id != Y
)
)
AND projects.id != X
X = ID of current project ('product category')
Y = ID of current 'user' (company)
But my real question is, how to do this elegantly in Laravel Eloquent (currently v4.2). I tried it, but I have no luck so far...
Update:
I should note that I do have experience using Eloquent and Models through multiple projects, but for some reason I just fail with this specific query. So was hoping to see an explained solution. It is a possibility that I'm thinking in the wrong way and that the answer is relatively easy.
You will need to utilize Eloquent relationships in order to achieve this. (Note that I am linking to the 4.2 docs as that is what you are using, but I would highly suggest upgrading Laravel to 5.1)
I am assuming you have a 'Company' and 'Project' model already. Inside each of those models, you need to a define a method that references its relationship to the other model. Based on your description, it sounds like the two have a Many to Many relationship, meaning that a company can have many projects and a project can also belong to many companies. You already have a database table linking the two. In the Eloquent ORM this linking table is called a pivot table. When you define your relationships in the model, you will need to pass the name of that pivot table as your second argument. Here's how it could look for you.
Company model:
class Company extends Model
{
/**
* Get the projects attached to a Comapny. Many To Many relationship.
*/
public function projects()
{
return $this->belongsToMany('Project','company_projects');
}
}
Project model:
class Project extends Model
{
/**
* Get the companies this project belongs to. Many To Many relationship.
*/
public function companies()
{
return $this->belongsToMany('Company','company_projects');
}
}
If your models have these relationships defined, then you can easily reference them in your views and elsewhere. For example, if you wanted to find all of the projects that belong to a company with an ID of 1234, you could do this:
$company = Company::find(1234);
$projects = $company->projects;
Even better, you can utilize something called eager loading, which will reduce your data lookup to a single line (this is mainly useful when passing data to views and you will be looping over related models in the view). So those statements above could be rewritten as:
$company = Company::with('projects')->find(123);
This will return your Company model with all its related products as a property. Note that eager loading can even be nested with a dot notation. This means that you can find all the models that link to your main model, and then all the models for those models, and so on and so forth.
With all of this in mind, let's look at what you specifically want to accomplish.
Let us assume that this method occurs in a Controller that is being passed a project id from the route.
public function showCompaniesAndProjects($id)
{
//Get all companies in the same project as you
//Use eager loading to grab the projects of all THOSE companies
//Result will be a Project Object with all Companies
//(and those projects) as Eloquent Collection
$companies = Project::with('companies.projects')->find($id);
//do something with that data, maybe pass it to a view
return view('companiesView')->with('companies',$companies);
}
After defining your relations in your models, you can accomplish that whole query in a single line.

Fetching all pivot table columns via custom pivot type

I have a many-to-many relationship where the pivot table has about 20 additional columns. I am using a custom pivot class, and I have successfully set up the code to return an instance of that class when the ->pivot property is accessed on the relation, e.g.
$supplier->products->pivot returns the custom pivot class.
However, when wanting to access the data, I can manually define all the individual attributes of the pivot class (which extends Pivot by the way) in the belongsToMany relationship like this:
return $this->belongsToMany(Product::class, ['prop1', 'prop2', 'prop3'])
...But, how can I retrieve all the pivot data of the class without manually defining them as it ties the relationship declaration very close to the class? Is this possible. If not, it's going to make maintainability a PITA! Ideally, it'd be really nice if withPivot just had a flag to get it all!
In my circumstances, I found it easier to separate all the data into a separate table and model, and add a foreign key in the pivot table to the additional table record. This allows me to use the 'normal' model handling in Laravel and means I don't have to mess around with problems like this!
My use case was a schema of product and supplier with a many-to-many, and each supplier having their own data for the product, namely price, stock, shipping cost / times etc, so I moved all this from the pivot to a SupplierProduct model.
I'll leave this question here, as although this isn't the direct answer to the question (which I fear the answer is 'no'), this is a solution which is viable and can save quite a bit of coding frustration!

Categories