I have a model called User which has a BelongsToMany relationship on it called lineManagers(). This relationship returns a collection of User models. The nature of this setup allows for a parent-child style relationship to operate on multiple levels.
The BelongsToMany uses a table called line_manager_user which has a simple schema of mapping a user id to a line manager user id:
| user_id | line_manager_id |
User A -> lineManagers()-> User B
User C -> lineManagers() -> User D
Depending upon certain permissions, I want to be able to query this relationship to multiple levels for users who have a line manager with a specific users.id, potentially using a whereHas() but I'm aware that this could be quite a detriment to performance.
I had tried the below query but to no avail (the last section is the relevant part):
$query = User::query()
->with('lineManagers')
->orderBy('first_name')
->orderBy('last_name')
->havingEmploymentStatus(UserEmploymentStatus::EMPLOYED)
->whereHas('lineManagers.lineManagers.lineManagers', function (Builder $query) {
$query->where('id', $this->getAuthenticatedUser()->id);
});
I don't specifically want 3 levels, ideally the query would retrieve lineManagers until an empty collection is hit. This does need to be something that I done at query level rather than collection level unfortunately
If you made it a function that called itself until it drilled down to the empty collection you could do it. That wouldn't matter how many levels there are then
Check out https://www.sitepoint.com/laravel-blade-recursive-partials/
I wouldn't recommend doing it in the blade file, rather work their "Plain old PHP" code into a function
Related
I have
users
id
username
companies
id
areas
id
area_company
id
area_id
company_id
area_company_user
id
area_company_id
user_id
company_user
id
company_id
user_id
area_user
id
area_id
user_id
where
one user has 0 to many areas AND one area can have 0 to many users
one area can have 0 to many companies AND one company can have 0 to many areas
one company can have 0 to many users AND one user can have 0 to many companies
one area_company can have 1 to many users AND one user can have 0 to many area_company
area_company_user has attributes specific to that kind of user
Also, I'm structuring the routes in the following manner
/users - all existing users
/areas - all existing areas
/companies - all existing companies
/areas/{area}/companies - all existing companies in a specific area
/users/{user}/companies - all existing companies from a specific user
/companies/{company}/areas - all existing areas the company is in
/areas/{area}/companies/{company}/users - all existing users from a company that exists in a specific area
For 1., 2. and 3. I'm creating controllers that follow the next pattern
AreaController with methods index(), create(), store(), show(), edit(), update() and destroy()
GET /areas, index() method,
GET /areas/create, create() method,
POST /areas, store() method,
GET /areas/{area}, show() method,
GET /areas/{area}/edit, edit() method,
PUT/PATCH /areas/{area}, update() method,
DELETE /areas/{area}, destroy() method.
There's now basically two cases left from that route list
Case 1: 4., 5. and 6.
Case 2: 7.
My question is, should I create new controllers for each case since I'd like to perform various actions in each? If yes, that'd mean, respectively
Case 1: AreaCompanyController, UserCompanyController and CompanyAreaController
Case 2: AreaCompanyUserController
Note: this was a helpful answer but didn't exactly address my concern.
You can see there is already something called nested resource in docs which can cover your cases 4,5,6. In docs there is PhotoCommentController which you've described that you're using it (question case 1).
For question case 2 you can for example make model for pivot table and have it that way for route and controller. For example AreaCompanyUserController if I understand well, you have here area_company pivot table with association of user. So many users can be part of same area_company association.
You can circumvent Area and Company models use with use of AreaCompany model that would be the pivot model. Knowing id of that pivot model you can easily get associating area, company and users.
class AreaCompany extends Pivot
{
/** code here */
}
Having this, you can name your resource route /area-companies and /area-companies/{$areaCompany}/users to avoid triple nesting.
I presume any of these decisions have own consequences like subdirectory planning for controllers, form request files, providers, sometimes even route files.
Probably heard and read for thousand times, but important is to stick with consistent way of it. Consider what could be lesser technical debt in some potential upgrade.
In order to simplify the debugging and maintenance process of the project it's better to define the controller for each case in case if anything goes wrong it's easy to debug and solve the problem so you will be having
AreaController , UserController , CompanyController
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')
}
I'm working on a project where I'm using Laravel Eloquent to fetch all my data from my database.
I'm storing basketball clubs, teams and players in different tables.
Now I want to select all basketball players that belong to a team that belongs to a club.
I created a relation in the Club model:
public function basketballPlayers()
{
return $this->hasManyThrough('App\Models\Basketball\BasketballPlayer','App\Models\Basketball\BasketballTeam','id','team_id');
}
And a user relation in the BasketballPlayer model:
public function user()
{
return $this->hasOne('App\User','id','user_id');
}
Now when I execute the following command, DB::getQueryLog() returns 3 Queries.
Club::findOrFail($club_id)->basketballPlayers()->with('user')->get();
But when I execute the following command without using relations, DB::getQueryLog() returns only 1 query.
$players = BasketballPlayer::Join('basketball_teams','basketball_teams.id','=','basketball_players.team_id')
->join('users','users.id','=','basketball_players.user_id')
->where('basketball_teams.club_id','=',$authUser->club_id)
->get(['basketball_players.*','users.firstname','users.lastname','basketball_teams.name']);
I don't need all the data from the club and the user (see ->get(...) in the Join query).
Is it possible to achieve the same result like in the "long" manual Join-query using the much cleaner and simpler Eloquent relation approach?
Thanks for your help!
You can read less data using with (not all user columns as example) by this
Club::findOrFail($club_id)
->basketballPlayers()
->with('user:id,firstname,lastname') // <- here is change
->get();
The id (and foreign keys) column is important. You should be also able to make nested with in following way (I write code from head - so please test it):
Club::findOrFail($club_id)
->with('BasketballPlayer:id,user_id')
->with('BasketballPlayer.user:id,firstname,lastname')
->get();
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.
My scenario is this:
I have a Course model
Each Course can have many CourseTopics, through a topics() relationship
Each CourseTopic can have many Lessons, through a lessons() relationship
Is there a compact way to retrieve and handle all the Lessons associated with a single Course (for example, to list them, or to count their total number)?
My aim would be to have a very brief syntax to use in Blade templates; I don't want to involve logic (or at least, keep it to a bare minimum) or raw SQL queries into my template.
What I've tried:
$course->with("topics.lessons")
where $course is the current instance of the course in a template, doesn't work (gives to me all the courses with all their topics and lessons).
EDIT:
A solution is to define a hasManyThrough() relationship like:
$this->hasManyThrough("Lessons", "CourseTopics");
This solves the problem for a 2-level nested relationship. How about a 3-level instead?