Laravel / hasMany / 3 tables single callback - laravel

I have 3 tables like so:
User:
- user_id
- name
Location_user:
- loc_id
- loc_loc_id
- loc_user_id
Location:
- location_id
- location_name
So the users may belong to 4 locations so the "location_user" table will hold 4 records for that user.
At the moment I can call the users and get back a list of there locations but on an ID only basis using:
public function locations()
{
return $this->hasMany('App\Location_user', 'loc_user_id');
}
As I said this will come back with all the locations this user belongs too but I want to get the names of those location instead of the ID's or even both.
Any ideas? I am fairly new to laravel still so not had to do this yet.
EDIT:
Called by:
$results = User::with('locations')->get();

What you want is a many to many relationship not hasMany. Change your function to
public function locations()
{
return $this->BelongsToMany('App\Location_user', 'Location_user');
}

Ended up using:
public function locations() {
return $this->belongsToMany('App\Location', 'location_user', 'loc_user_id', 'loc_loc_id')->withTimestamps('loc_updated','loc_created');
}
Because I used custom column names I specified these and also included time-stamps as I needed those

Related

Laravel relationship between 3 tables where 2 are connected via pivot

I am new to Laravel and looked already for a similiar thread but I didnt find something.
I want to use Eloquent and I got 3 Models and Tables: Testseries, Devices and Users.
The Users has a many to many relation to devices. (One User has many devices and vica versa)
And Devices has a one to many relation to testseries. (One Device has many testseries and many testeries has one device)
**
Table structure Users:**
id
username
Table structure Devices:
id
serial_number <-- its a string, not important for the structure
Table structure Testseries:
id
device_id
Devices and Users are connected via Pivot
device_user:
id
user_id
device_id
If a User is logged in, I want to show all Testseries from all Devices that are connected to the User.
I defined in the User Model:
public function devices(): \Illuminate\Database\Eloquent\Relations\BelongsToMany {
return $this->belongsToMany(Device::class);
}
And in the Device Model:
public function users(): \Illuminate\Database\Eloquent\Relations\BelongsToMany {
return $this->belongsToMany(User::class);
}
public function testseries(): \Illuminate\Database\Eloquent\Relations\HasMany {
return $this->hasMany(Testserie::class);
}
Is there any way to create function inside the User Model which can easily access to the testserie?
If someone doesnt understand what I want because my English isnt good. This function should tell what I want inside the User Model:
public function testseries() {
return $this->devices()->testseries();
}
Also I want all testseries at one query.
I tried with the each method. But its doing for each device a single query to the testserie.
I also tried it with the with method. It works, but I want all Columns from the Testseries Table, but then I have to tell all table names inside the array and I dont want the columns from the Devices table.
I expected to get a query when I call the ->get Method that I'll get all Testseries at once with a single query.
Try this :
Auth::user()->with('devices.testseries')->get();
I found a way,
I added this function to my User Model:
public function testseries(): \Illuminate\Support\Collection {
return Testserie::whereHas('device', function(Builder $query){
$query->whereHas('users', function(Builder $query){
$query->where ('user_id', '=', \Auth::user()->getKey());
});
})->get();
}
This works. I dont know if its the cleanest way. If someone has a better way. Dont be shy to share your thoughts.

Laravel model to get specific piece of data

I am trying to make a function that will help me get needed data quickly.
With all the trials I have been able to get to the following
Tables:
Users (id,name)
Projects (id,name)
User-Project (id, user_id, project_id, manager) where manager is a boolean , there can only be one manager for each project (but employees can still see the project reason why we have a pivot table, manager = 0 for other normal users that can access that project)
In the Project Model I have:
public function Manager(){
return $this->belongsToMany('App\User')->wherePivot('manager', true);
}
In the View I have:
<p><strong>Project Manager:</strong> {{$project->manager}}</p>
On the actual page I get:
Project Manager: [{"id":4,"name":"Daniel Doe","email":"danieldoe#hotmail.com","phone":"70846556","email_verified_at":null,"created_at":"2020-12-20 21:05:50","updated_at":"2020-12-20 21:05:50","pivot":{"project_id":1,"user_id":4,"manager":1}}]
When I change the view to:
<p><strong>Project Manager:</strong> {{$project->manager[0]->name}}</p>
I get:
Project Manager: Daniel Doe
This is what I actually want but I would like to do it from the model if possible. So I tried:
public function Manager(){
return $this->belongsToMany('App\User')->wherePivot('manager', true)->first()->name;
}
But I get the following error:
must return a relationship instance
Can this be done from the model's function?
You can keep your defined relationship, but to access ->first()->name, you'll need to use an "Accessor":
public function manager() {
return $this->belongsToMany('App\User')->wherePivot('manager', true);
}
public function getManagerNameAttribute() {
return $this->manager->first() ? $this->manager->first()->name : 'No Manager';
}
Then, in your code, you simple access:
{{ $project->manager_name }}
If your manager() function returns a Collection of at least 1 record, it will return the name, otherwise it will display 'No Manager' as a fallback.
If you don't want to change the structure of this you can use an accessor to get this information, roughly something like this:
class Project ...
{
public function users()
{
return $this->belongsToMany(...)->withPivot(...);
}
public function getManagerAttribute()
{
return $this->users()->wherePivot('manager', 1)->first()?->name;
}
}
You can do this in different ways, you could use the loaded users relation and use a the Collection methods to filter the manager. You could create another relationship called managers that uses the wherePivot off of users(), etc ...
The only thing to worry about with this setup is that every call to $model->manager would be causing that query, so it may be a good idea to create another relationship manager so that you can load that once and keep using it without the need to keep querying the database:
public function managers()
{
return $this->users()->wherePivot(...);
}
public function getManagerAttribute()
{
return $this->managers->first()?->name;
}
Though, as mentioned already it is probably better to have something like a manager_id on the Project itself.

hasOneThrough and hasManyThrough not working in Eloquent

I have the following tables
users userdatas workdays
--------- ----------- --------
id id id
user_id
workday_id
I would like to get a user's workdays (and later get all the users that have a specified workday, but one step at a time). I attempted several variations of this however it's not working. I believe the issue could be that workdays doesn't reference userdatas.
In the User model:
public function workday() {
return $this->hasOneThrough('App\Workday', 'App\Userdata');
}
I've also defined the following:
In Userdata
public function workday() {
return $this->hasOne('App\Workday');
}
In Workday
public function userdatas() {
return $this->belongsToMany('App\Userdata');
}
Does the reverse through relationship have to be defined in Workday? Any tips to get this to work?
Thank you!
You're following wrong relationship. As per your Database table design it should be Many to many relationship(belongsToMany).
In User Model pass the relationship as below.
public function workdays() {
return $this->belongsToMany('App\Workday', 'userdatas','user_id','workday_id');
}

How can I make a relation from Table A to table C where A related to B and B is related to C in LARAVEL

How can I able display the info from "set Penalties" Table when I do something like: $loanApplication->duration->setPenalties->penalty?
LoanApplication Model:
public function loanDuration()
{
return $this->hasOne('App\LoanDuration', 'id','loan_duration_id');
}
LoanDuration Model:
public function loanApplications()
{
return $this->hasMany(LoanApplication::class);
}
You are almost there. $loadApplication->loadDuration should already give you information from the loan_durations table.
In your LoanDuration Model you should add a new relationship method like this:
public function setPenalties(): BelongsTo
{
return $this->belongsTo(<Your penalty model>);
}
This will allow you to retrieve the penalties from a LoadDuration:
$loadDuration->setPenalties;
Or with the full example, the result will be a collection of setPenalties:
$loanApplication->loadDuration->setPenalties
It could be possible that you wont get any results from the setPenalties. This is because of the name of the related column name in the database. You could either change the name of the column duration_id to loan_duration_id or specify the duration_id in the $this->belongsTo(<Your penalty model>, 'duration_id')

How to GROUP and SUM a pivot table column in Eloquent relationship?

In Laravel 4; I have model Project and Part, they have a many-to-many relationship with a pivot table project_part. The pivot table has a column count which contains the number of a part ID used on a project, e.g.:
id project_id part_id count
24 6 230 3
Here the project_id 6, is using 3 pieces of part_id 230.
One part may be listed multiple times for the same project, e.g.:
id project_id part_id count
24 6 230 3
92 6 230 1
When I show a parts list for my project I do not want to show part_id twice, so i group the results.
My Projects model has this:
public function parts()
{
return $this->belongsToMany('Part', 'project_part', 'project_id', 'part_id')
->withPivot('count')
->withTimestamps()
->groupBy('pivot_part_id')
}
But of course my count value is not correct, and here comes my problem: How do I get the sum of all grouped parts for a project?
Meaning that my parts list for project_id 6 should look like:
part_id count
230 4
I would really like to have it in the Projects-Parts relationship so I can eager load it.
I can not wrap my head around how to do this without getting the N+1 problem, any insight is appreciated.
Update: As a temporary work-around I have created a presenter method to get the total part count in a project. But this is giving me the N+1 issue.
public function sumPart($project_id)
{
$parts = DB::table('project_part')
->where('project_id', $project_id)
->where('part_id', $this->id)
->sum('count');
return $parts;
}
Try to sum in Collection,
$project->parts->sum('pivot.count');
This is best way I found. It's clean (easy to read) and able to re-use all of your scope, ordering and relation attribute caching in parts many-to-many defination.
#hebron No N+1 problem for this solution if you use with('parts') to eager load. Because $project->parts (without funtion call) is a cached attribute, return a instance of Collection with all your data. And sum('pivot.count') is a method of Collection which contains pure funcional helpers (not relative to database, like underscore in js world).
Full example:
Definition of relation parts:
class Project extends Model
{
public function parts()
{
return $this->belongsToMany('Part', 'project_part', 'project_id', 'part_id')
->withPivot('count')
->withTimestamps();
}
}
When you use it (note that eager load is important to avoid N+1 problem),
App\Project::with('parts')->get()->each(function ($project) {
dump($project->parts->sum('pivot.count'));
});
Or you can define the sum function in Project.php,
class Project extends Model
{
...
/**
* Get parts count.
*
* #return integer
*/
public function partsCount()
{
return $this->parts->sum('pivot.count');
}
}
If you want to avoid with('parts') on caller side (eager load parts by default), you can add a $with attribute
class Project extends Model
{
/**
* The relations to eager load on every query.
*
* #var array
*/
protected $with = ['parts'];
...
}
From the code source:
We need to alias all of the pivot columns with the "pivot_" prefix so we can easily extract them out of the models and put them into the pivot relationships when they are retrieved and hydrated into the models.
So you can do the same with select method
public function parts()
{
return $this->belongsToMany('Part', 'project_part', 'project_id', 'part_id')
->selectRaw('parts.*, sum(project_part.count) as pivot_count')
->withTimestamps()
->groupBy('project_part.pivot_part_id')
}
The best way that you can use is:
$project->parts->sum('pivot.count');
I faced the same problem, but this solved my issue.

Resources