1

I have a module of friendship request in my project. Below 3 tables are being used in it:-

  1. Users
  2. User_profile
  3. Friendship

Users :- Id,slug,Name,Email, Password

UserProfile :- Id, user_slug, Profile_pic, DOB..etc.

Friendship :- Id, User_slug, Friend_slug, Status

Relationships:-

User Model:-

public function Profile(){
    return $this->hasOne('UserProfile','user_slug','slug')->first();
}

public function sentFriendshipRequests(){
    return $this->hasMany('Friendship','user_slug','slug');
}

public function receivedFriendshipRequests(){
    return $this->hasMany('Friendship','friend_slug','slug');
}   

UserProfile Model:-

public function User(){
    return $this->belongsTo('User','user_slug','slug');
}

Friendship Model:-

public function receiver(){
    return $this->belongsTo('User','friend_slug','slug');
}


public function sender(){
    return $this->belongsTo('User','user_slug','slug');
}

Goal:- I want to display list of pending friendship request received by an user.

Data Required:- All friendship request with pending status for current logged user & Name,Slug,Profile_pic of friendship request sender.

My Approach:-

$friendship_requests= Auth::user()->receivedFriendshipRequests();

foreach($friendship_requests as $frnd_req)
{
    $sender_user=User::where('slug',$frnd_req->user_slug());
}

Is there any other proper way to get this data by using Eloquent Relationship approach,without using join. I means how to get data using HasOne and HasMany relationship in one single query.

Any help or advice is greatly appreciated.

Thanks

Bit_hunter
  • 789
  • 2
  • 8
  • 25

1 Answers1

1

This is a self referencing many-to-many relationship, so you don't need those hasMany/belongsTo relations at all. You can simply use one belongsToMany for own requests and another one for received requests.

Read this first: https://stackoverflow.com/a/25057320/784588

Then add these relationships:

// pending requests of mine
function pendingFriendsOfMine()
{
  return $this->belongsToMany('User', 'friendship', 'user_slug', 'friend_slug')
     ->wherePivot('accepted', '=', 0)
     ->withPivot('accepted');
}

// pending received requests
function pendingFriendOf()
{
  return $this->belongsToMany('User', 'friendship', 'friend_slug', 'user_slug')
     ->wherePivot('accepted', '=', 0)
     ->withPivot('accepted');
}

// accessor allowing you call $user->friends
public function getPendingFriendsAttribute()
{
    if ( ! array_key_exists('pendingFriends', $this->relations)) $this->loadPendingFriends();

    return $this->getRelation('pendingFriends');
}

protected function loadPendingFriends()
{
    if ( ! array_key_exists('pendingFriends', $this->relations))
    {
        $pending = $this->mergePendingFriends();

        $this->setRelation('pendingFriends', $pending);
    }
}

protected function mergePendingFriends()
{
    return $this->pendingFriendsOfMine->merge($this->pendingFriendOf);
}

then yuou simply load it using nested relations:

$user = Auth::user();
$user->load('pendingFriendsOfMine.profile', 'pendingFriendOf.profile');
// the above will execute 4 queries - 2 for requests, 2 for related profiles

$pendingFriends = $user->pendingFriends; // for all pending requests
// or
// $user->load('pendingFriendOf.profile'); // 2 queries in this case
// $pendingRequests = $user()->pendingFriendOf; // for received requests only

foreach ($pendingFriends as $user) {
  $user->profile; // eager loaded profie model
}

Also, here a few errors you have in your code:

// there can't be first() in the relation definition
// and it is not needed anyway
public function Profile(){
    return $this->hasOne('UserProfile','user_slug','slug')->first();
}



// You never want to run this User::where() ...
// in a foreach loop, for it will result in n+1 queries issue
// You need eager loading instead.
foreach($friendship_requests as $frnd_req)
{
    $sender_user=User::where('slug',$frnd_req->user_slug());
}
Community
  • 1
  • 1
Jarek Tkaczyk
  • 78,987
  • 25
  • 159
  • 157
  • thanks for your detailed answer. I will try to implement this logic in my application. Hats off man :) – Bit_hunter Jan 03 '15 at 17:59
  • Thanks for help.... I am following your approach,but I am not getting pending friends detail. It is returning null. `$user = Auth::user(); $user->load('pendingFriendsOfMine.profile', 'pendingFriendOf.profile'); $pendingFriends = $user->pendingFriends; foreach ($pendingFriends as $user) { var_dump($user->profile); }' Should I call `$user->loadPendingFriends()` method before calling `$pendingFriends = $user->pendingFriends;` – Bit_hunter Jan 04 '15 at 07:13
  • Get random user from `pendingFriendsOfMine` and check if he has `profile`. It should return `null` for those users, who don't have related profile. And btw I fixed FK typos. – Jarek Tkaczyk Jan 04 '15 at 09:47
  • Thanks for help. I am using this code:- `$user = Auth::user(); $user->load('pendingFriendOf.Profile'); $friendshipRequests = $user->pendingFriendOf; ` But now how can I get Friendship Id and Profile details of the users who have sent friendship request? – Bit_hunter Jan 04 '15 at 16:11
  • **1** Why would you need that friendship id? It's just a pivot table.. `pendingFriendOf` is a collection of `User` models with `pivot` object attached, so you call `->pivot->id` (but you need to use `withPivot('id')` on the relation definition for this. **2** profile details are accessed like in my example. It all works, so make sure you didn't mess something up. – Jarek Tkaczyk Jan 04 '15 at 16:18
  • **1.** When user sends friendship request to user, One notification will be sent to receiver of this new friendship request.Friendship id will be used to generate a link to accept or reject this friendship request. **2**. I means how can i access the profile detail? Should i use `$pendingFriendsOf->profile`? – Bit_hunter Jan 04 '15 at 16:26
  • Thanks..Finally I am able to get Friendship_id and profile details...Thanks alot man.. You have saved my day..I was pulling my hairs from last 2 days.. Finally I got solution. One more doubt is there `is it necessary to use `$user->load('pendingFriendOf.Profile');` code? Cant we use `$friendshipRequests = $user->getPendingFriendsAttribute();` directly? – Bit_hunter Jan 04 '15 at 16:37
  • If you use it directly (ie. without eager loading nested relation) then you will end up with 1 query per each item in that collection (each pending user), because the profile is never loaded for all the users at once. So, yes, it is necessary. – Jarek Tkaczyk Jan 04 '15 at 16:58
  • I have one doubt about eager loading. As per Laravel Official Doc `Book::with('authors.followers)` will return all authors with authors followers details far all books. If I want to get Author and its followers for 1 specific book, I have to use your above mentioned approach always. Will this code work `$book=Book::find(1); $author_with_followers=$book::with('author.follower')` ? – Bit_hunter Jan 04 '15 at 18:27
  • `$model->load('relation.relation2')` is just the same as `$model = Model::with('relation.relation2')->find($id)`. The only difference is that `load` is called on already fetched instance (or on the collection), while `with` is used when building the query. You use `Auth::user()` that's why there's `load` in my example. – Jarek Tkaczyk Jan 04 '15 at 21:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/68195/discussion-between-bit-hunter-and-jarek-tkaczyk). – Bit_hunter Jan 05 '15 at 04:58