Laravel eloquent relationships, 3 way join - laravel

I have 3 tables (simplified here)
users
id, name, email
teams
id, name
team_user
team_id, user_id
I want to send an API query to return all teams a user Id belongs to, and what other members are also in that team. Rather than returning just userIds, I want to fill the arrays with their actual user data, e.g name and email.
Route::get('/user/{id}/teams/', 'UserController#getTeams');
User.php (model)
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
public function teams()
{
return $this->belongsToMany('App\Team', 'team_user', 'team_id', 'user_id');
}
}
Team.php (model)
class Team extends Model
{
public function users()
{
return $this->belongsToMany('App\User', 'team_user', 'team_id', 'user_id');
}
}
TeamUser.php (model)
class TeamMember extends Model
{
public function user()
{
return $this->hasOne('App\User');
}
public function team()
{
return $this->hasOne('App\Team');
}
}
UserController.php
class UserController extends Controller
{
public function getTeams($userId) {
return User::find($teamId)->teams()->get();
}
}
This returns:
[
{
"id": 6,
"name": "P12",
"location": "Newbury",
"owner": 6,
"active": 1,
"created_at": "2017-12-20 10:18:00",
"updated_at": "2017-12-20 10:18:00",
"pivot": {
"team_id": 6,
"user_id": 6
}
},
{
"id": 4,
"name": "fwffewfffffff",
"location": "ffffffff",
"owner": 4,
"active": 1,
"created_at": "2017-12-19 19:56:27",
"updated_at": "2017-12-19 19:56:27",
"pivot": {
"team_id": 6,
"user_id": 4
}
}
]
However I would like to also include a list of other users in each of those 2 teams. With their names and emails (from the users table), not just the user_ids.
Is this possible without doing further seperate queries?

You would be able to eagerly load the relations and attach them to the model.
By using the dot notation teams.users, the output will include all users of all teams attached to user with id $userId.
I've added the findOrFail to make sure it will return a 404 when user can not be found.
class UserController extends Controller
{
public function getTeams($userId)
{
return User::with('teams.users')->findOrFail($userId);
}
}
This will return the User and attach the relations.
If you want to just return the teams, you can do something like this:
class UserController extends Controller
{
public function getTeams($userId)
{
$user = User::with('teams.users')->findOrFail($userId);
// $user->teams will hold a collection of teams with their users
return $user->teams;
}
}

Try using with() to retrieve the fields from different tables, for example
class UserController extends Controller {
public function getTeams($userId) {
return User::find($userId)->with('teams')->get();
// return User::find($userId)->teams()->get();
}
}
If you would like to select specific column from the team_members database, you could add function inside with, for example
class UserController extends Controller {
public function getTeams($userId) {
return User::find($userId)->with(['teams' => function($query) {
$query->select('id', 'name');
}])->get();
}
}

For now I have solved this with
class UserController extends Controller
{
public function getTeams($userId) {
$teamWithMembers = [];
$teams = User::find($userId)->teams()->get();
foreach($teams as $team) {
$team->members = Team::find($team->id)->users()->get();
$teamWithMembers[] = $team;
}
return $teamWithMembers;
}
}

Related

Getting multiple related models together

In my Laravel app, I have three models, User, Course and CourseScore. I want to get all courses for an specific user with his score. something like:
{
id: 1,
name: first_name,
corses: [
{
id: 1,
name: 'course one',
score: 17, // this is what i need for every course,
},
{
id: 2,
name: 'course two',
score: 19, // this is what i need for every course,
},
]
}
Here are my Models:
User
<?php
class User extends Authenticatable
{
public function courseScores()
{
return $this->hasMany(CourseScore::class);
}
public function courses()
{
return $this->belongsToMany(Course::class);
}
}
Course
<?php
class Course extends Model
{
public function courseScores()
{
return $this->hasMany(CourseScore::class);
}
public function users()
{
return $this->belongsToMany(User::class);
}
}
CourseScore
<?php
class CourseScore extends Model
{
protected $table = 'course_scores';
public function course()
{
return $this->belongsTo(Course::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
And of course, please let me know if the relations are incorrect.
If I understand it correctly, you can do something like this,
in your User model
public function courses(){
return $this->hasMany(Course::class); // changed belongsToMany to hasMany
}
in your Course model
public function users(){
return $this->belongsTo(User::class); // changed belongsToMany to belongsTo
}
In your controller
$usersData = User::with('courses.courseScores')->get();
dd($usersData);
I dont fully understand your table structure so Im assuming you have multiple row of Scores for single Course (One to Many).
If the relationship is one is to one then add in your Course Model
public function courseScores(){
return $this->hasOne(CourseScore::class); //changed hasMany to hasOne
}
You can try using this
$val = DB::table('course as c')
->join('user as u', 'u.id','c.id')
-join('courseScore as cs','u.id','cs.id')
->get();
Then just dd($val) to see what value it shown

Laravel eloquent - one to one relation with different table

I have three tables,
1. user (id, name)
2. state (id, stateName, code)
3. user_relations(id, user_id, state_id)
3 models,
1. User
2. State
3. UserRelation
By logic, User has one UserRelation and UserRelation has one State
I can get user state by
User::first()->relation->state
What I want is call this by User::with('state')->first().
If you would like to keep the current db schema like it is, you may use Laravel accessors
Accessors and mutators allow you to format Eloquent attribute values
when you retrieve or set them on model instances.
class User
public function getStateAttribute($value) {
return $this->relation->state;
}
Usage
User::findOrFail($id)->state;
Edit
User Model
class User extends Authenticatable
{
//...
public function state() {
return $this->relation()->with('state');
}
public function relation() {
return $this->hasOne('App\UserRelation', 'user_id');
}
}
UserRelation Model
class UserRelation extends Model
{
protected $table = 'user_relations';
public function state() {
return $this->belongsTo('App\State'); // must be belongsTo instead of hasOne
}
}
State Model
class State extends Model
{
protected $table = 'states';
}
Controller
App\User::with('state')->find(1);
{
"id": 1,
"name": "Test",
"email": "test#test.com",
"email_verified_at": "2020-02-13 00:00:00",
"created_at": "2020-02-20 00:00:00",
"updated_at": null,
"state": {
"id": 2,
"state_id": 2,
"user_id": 1,
"state": {
"id": 2,
"stateName": "state 2",
"code": "code 2"
}
}
}
If user can only have one state,
add a state_id in the user table and get rid of the third table user_relations
class User extends Model
{
public function state()
{
return $this->hasOne('App\State');
}
}
class State extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
In this way, you can
$state = User::find(1)->state;
$user = State::find(1)->user;
First way:
You have to define state_id in users table for one to one relation.
class User extends Model
{
public function state()
{
return $this->hasOne('App\State');
}
}
Or if you need same database structure, then
Second Way:
Basically it's Many to Many relations between User and State.
So better way is to define many to many relation function in Models of User and State considering UserRelation a middle(pivot) table.
class User extends Model
{
public function states()
{
return $this->belongsToMany('App\State', 'user_relations');
}
}
class State extends Model
{
public function users()
{
return $this->belongsToMany('App\User', 'user_relations');
}
}
and then to access states of user, you can call:
$user->states->first();
It's ok that you need one to one relation, you can call first() method to fetch first record od state.
Simillery to access user of state
$state->users->first();
And if you need to access user_relations (one to one) only, you have to define methods in both user and state model:
class User extends Model
{
public function user_relation()
{
return $this->hasOne('App\UserRelation');
}
}
class State extends Model
{
public function user_relation()
{
return $this->hasOne('App\UserRelation');
}
}
Since user and state are many to many, you can user belongsToMany function inside User model.
public function states(){
return $this->belongsToMany(State::class, 'user_relations', 'user_id', 'state_id');
}
You can get Eager Loading by retrieving both models related to the user like:
$user = User::with('states')->first();
And now to retrieve states, you can loop through the states like:
foreach($user->states as $state){
//Retrieved state model variable
....
}
If you are doing one-to-one relationship you won't need a pivot table, in your case the user_relations table. Pivot tables are used to express many-to-many relations.
And for the solution you want. You will have to store the user_id in the state table.
so your state table should look like the following:
state (id, user_id, stateName, code)
Relations should be like:
User model:
public function state()
{
return $this->hasOne('App\State');
}
State model:
public function user()
{
return $this->belongsTo('App\User');
}

Laravel querying polymorphic relationships with nested many to many items

I have 4 tables with the following relationships:
class Note extends Model
{
public function noteable() {
return $this->morphTo();
}
}
class Expense extends Model
{
public function notes()
{
return $this->morphMany(Note::class, 'noteable');
}
}
class Review extends Model
{
public function notes()
{
return $this->morphMany(Note::class, 'noteable');
}
public function repairs()
{
return $this->belongsToMany(Repair::class);
}
}
class Repair extends Model
{
public function reviews()
{
return $this->belongsToMany(Review::class);
}
}
as you can see Note is in the relationship one to many polymorphic and Reviews is in many to many with Repairs.
I would like to take all Notes that Reviews also has Repairs. How do I do such an operation?
Following the documentation I am trying to do something like this:
$notes = App\Note::query()
->with(['noteable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
Review::class => ['repairs']
]);
}])->get();
I would like to my query should return something like:
[
{
"id": 11,
"noteable_id": 4,
"noteable_type": "App\\Expense",
"noteable": {
"id": 4,
"name": "Expense",
"category": "general"
}
},
{
"id": 13,
"noteable_id": 5,
"noteable_type": "App\\Review",
"noteable": {
"id": 5,
"name": "Review 5",
"mileage": 120000,
"repairs": [..., ...] //what I need
}
}
]
To load your Repair models for your Reviews, try this:
if ($type == 'App\Review') {
$query->with('repairs')
}
EDIT :
I've just done some more reading on whereHasMorph and I think i was mistaken as to what it is doing. It is at it's most basic level a where clause, designed to constrain the results of the query. What I have suggested above would the the equivalent of joining something to a MySQL subquery. Not what we want!
What you want to do is actually under
Nested Eager Loading morphTo Relationships
(https://laravel.com/docs/6.x/eloquent-relationships#querying-polymorphic-relationships)

How to attach last records in relation with eloquent in Laravel

I have a group table, messages table, and users table.
The messages table belongs to one user and morphTo a group.
the groups table belongs to one user and morphMany messages.
I want to get all groups related to one user with the last messages "only the last message".
`users Model`
public function messages(){
return $this->hasMany('App\Message');
}
public function groups(){
return $this->belongsToMany('App\Group', 'group_user');
}
`Groups Model`
public function user(){
return $this->belongsTo('App\User');
}
public function messages(){
return $this->morphMany('App\Message', 'messagable');
}
`messages model`
public function user(){
return $this->belongsTo('App\User');
}
public function messagable(){
return $this->morphTo();
}
I want the result to be like that:
{
"id": 23,
"user_id": 2,
"name": "Group1",
"description": "5656",
"img": "images/1524958644.png",
"created_at": "2018-04-28 23:37:24",
"updated_at": "2018-04-28 23:37:24",
"last_messages":{
"id": 292,
"user_id": 1,
"messagable_type": "App\\Group",
"messagable_id": 23,
"message": "hi",
"created_at": "2018-04-29 07:48:55",
"updated_at": "2018-04-29 07:48:55"
}
}
I would use an accessor for this.
eg. In your Groups model
protected $appends = ['last_message'];
public function getLastMessageAttribute()
{
return $this->messages->orderBy('id', 'desc')->first();
}
You could also use orderBy with created_at instead of id
You can then retrieve it by using this as an example:
Group::where('user_id',2)->first()->last_message;

Laravel: using "morphMany" and joining another table?

I'm new to laravel too. I have a question about Eloquent:
table structures:
User: id, username
Posts: id, user_id, content
Likes: id, likeable_type, likeable_id, user_id
(Because of I want to make 'Likes' table extendable for comments, too.)
Models\Likes.php
class Likes extends Model
public function likeable()
{
return $this->morphTo();
}
Models\Post.php
class Posts extends Model
public function likes()
{
return $this->morphMany("\App\Models\Likes","likeable");
}
And when I call
$post = Posts::find($id)->likes()->get();
in my controllers, it will return like this:
"likes": [
{
"id": 1,
"likeable_type": "App\\Models\\Posts",
"likeable_id": 1,
"user_id": 1,
},
{
"id": 2,
"likeable_type": "App\\Models\\Posts",
"likeable_id": 1,
"user_id": 1,
}]
But how can I get results like this:
"likes": [
{
"id": 1,
"likeable_type": "App\\Models\\Posts",
"likeable_id": 1,
"user_id": 1,
"username":"chenhui",//join user on user.id = likes.user_id
}...
Many thanks and sorry for my poor english!
I believe in your Likes model you need
class Likes extends Model
public function user()
{
return $this->belongsTo('App\Models\User');
}
and then it should be:
$post = Posts::find($id)->likes()->with('user')->get();

Resources