24

I'm trying to create a Friendship system with Laravel (I'm starting with it) but I'm blocked with relationships. Here's the thing : there is one table Users and one table Friends which contains the following columns :

friends: id, user_id, friend_id, accepted.

It looks like a Many to Many so here's what I set on User class :

class User extends Eloquent {
    function friends()
    {
        return $this->belongsToMany('User');
    }
}

But when I try a :

$friends = User::find($id)->friends()->get()

I have this error :

Base table or view not found: 1146 Table 'base.user_user' doesn't exist

I would like to get a list of the Friends of a user, no matters if the user sent the invitation or received it. So the user can ba on user_id or on friend_id and then I retrieve the data of the other user depending of that column.

Any idea? Thank's!

EDIT : Here's the code I use :

$usersWithFriends = User::with('friendsOfMine', 'friendOf')->get();
$user = User::find(Auth::id())->friends;

foreach($user as $item) {
    echo $item->first()->pivot->accepted;
} 
Madnx
  • 1,532
  • 4
  • 16
  • 23

2 Answers2

56

tldr; you need 2 inverted relationships to make it work, check SETUP and USAGE below


First off the error - this is how your relation should look like:

function friends()
{
  return $this->belongsToMany('User', 'friends', 'user_id', 'friend_id')
    // if you want to rely on accepted field, then add this:
    ->wherePivot('accepted', '=', 1);
}

Then it will work without errors:

$user->friends; // collection of User models, returns the same as:
$user->friends()->get();

SETUP

However you would like the relation to work in both ways. Eloquent doesn't provide a relation of that kind, so you can instead use 2 inverted relationships and merge the results:

// friendship that I started
function friendsOfMine()
{
  return $this->belongsToMany('User', 'friends', 'user_id', 'friend_id')
     ->wherePivot('accepted', '=', 1) // to filter only accepted
     ->withPivot('accepted'); // or to fetch accepted value
}

// friendship that I was invited to 
function friendOf()
{
  return $this->belongsToMany('User', 'friends', 'friend_id', 'user_id')
     ->wherePivot('accepted', '=', 1)
     ->withPivot('accepted');
}

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

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

protected function loadFriends()
{
    if ( ! array_key_exists('friends', $this->relations))
    {
        $friends = $this->mergeFriends();

        $this->setRelation('friends', $friends);
    }
}

protected function mergeFriends()
{
    return $this->friendsOfMine->merge($this->friendOf);
}

USAGE

With such setup you can do this:

// access all friends
$user->friends; // collection of unique User model instances

// access friends a user invited
$user->friendsOfMine; // collection

// access friends that a user was invited by
$user->friendOf; // collection

// and eager load all friends with 2 queries
$usersWithFriends = User::with('friendsOfMine', 'friendOf')->get();

// then
$users->first()->friends; // collection

// Check the accepted value:
$user->friends->first()->pivot->accepted;
ymutlu
  • 6,585
  • 4
  • 35
  • 47
Jarek Tkaczyk
  • 78,987
  • 25
  • 159
  • 157
  • Thank you for the answer, I didn't really get that I could do such relations (merging, inverted etc.). Works like a charm and now I'll be able to use the same sorts of relations for my future works! :) – Madnx Jul 31 '14 at 16:32
  • I have a last question: when using $users->friends, how can I know the accepted attribute (I remove the whenPivot to select all of the friends, accepted or not). – Madnx Jul 31 '14 at 17:14
  • Check the edit - you need to 1 add `withPivot()` to the relation, and 2 you can access it calling `friend->pivot->accepted` - See last example in the answer I gave. – Jarek Tkaczyk Jul 31 '14 at 17:18
  • 1
    Thank you. I edited my first post with my code because I can't make it work :/. EDIT : By removing the ->wherePivot() it works, do I have to keep it? – Madnx Jul 31 '14 at 17:23
  • 2
    Please let us know how to paginate this? $user->friends()->paginate(25) will not work. – Ketan Yekale Dec 25 '18 at 08:28
5

It's oviously a problem in your DB and also definition of the relation. Many-to-Many relation type expects you to use and intermediate table. Here's what you have to do :

  1. Create a user_friend (id, user_id, friend_id) table in your schema.
  2. Remove unnecessary fields from user and friend tables.
  3. Create proper foreign keys . user.id-> user_friend.user_id , friend.id -> user_friend.friend_id
  4. Better define full relation on the User and Friend models,

for example :

 class User extends Eloquent {
    function friends()
    {
        return $this->belongsToMany('User', 'user_friend', 'user_id', 'friend_id');
    }
}

You can read much more in Laravel docs, HERE

moonvader
  • 19,761
  • 18
  • 67
  • 116
Vit Kos
  • 5,535
  • 5
  • 44
  • 58
  • Thank you that really helped me understand how worked Many-to-Many. I just come from CodeIgniter and there's a lot to learn with such relations (which are interested). However I'm gonna accept the second answer, yours asks for one more table which I don't need in this case :/. – Madnx Jul 31 '14 at 16:31
  • 2
    The OP only has a single table called "User", there is no "Friend" table. – Nick Coad Jul 20 '15 at 04:32
  • 1
    I am facing this problem too. For many-to-many relationship we need to have an intermediate table. But in this case we already have users table which stores users information, so why we need to create separate friend table. We can create user_friend table which consist user_id field from users table and friend_id field and status field. How it's going to work with this? – vivek321 Apr 24 '17 at 10:58