Friendship System with Laravel:Many to Many Relationship

Friendship system with Laravel : Many to Many relationship

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;

Laravel 5.2 Friendship system: Many to Many relationship

You can define relationship on User model:

class User extend Model
{
...

// Get list of who sent me a friend request
public function myFriends()
{
return $this->belongsToMany(get_class($this), 'friends', 'friend_id');
}

// Get the list of friends whom I invited
public function friendsOf()
{
return $this->belongsToMany(get_class($this), 'friends', 'user_id', 'friend_id');
}

// Merge
public function getFriendsAttribute()
{
return $this->myFriends->merge($this->friendOf);
}
}

Then you can get the list of user's friends by $user->myFriends, to query friend by their first name: $user->myFriends()->where('first_name', '=', 'John')->get().

Laravel Eloquent relationship for user to friends

migration:

public function up()
{
Schema::create('friend_user', function(Blueprint $table) {
$table->increments('id');
$table->integer('friend_id')->unsigned()->index();
$table->integer('user_id')->unsigned()->index();
$table->foreign('friend_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}

User model:

public function friends()
{
return $this->belongsToMany(User::class, 'friend_user', 'user_id', 'friend_id');
}

UPDATE

Get all friends:

Auth::user()->friends (or Auth::user()->friends()->get())

Add friends:

Auth::user()->friends()->attach([2,3,4]); // Add user_id 2, 3 and 4

Remove friends:

Auth::user()->friends()->detach([2]); // Remove user_id = 2

Sync friends:

Auth::user()->friends()->sync([7]); // Remove old and add user_id = 7

Laravel ORM Friendship Relationship without duplication

You shouldn't try and turn what should be two rows into one row, but if you are going to try and do that then you definitely don't need to hit the database twice:

select * from users where (user_id = :user_id and friend_id = :friend_id) or  (friend_id = :friend_id and user_id = :user_id)

That in Laravel would be:

Users::whereRaw('(user_id = ? and friend_id = ?) or (friend_id = ? and user_id = ?)', [            
$user_id,
$friend_id,
$friend_id,
$user_id
]);

You could also do sub-wheres to group them, but thats a little complicated.

Laravel 5.5 User model and friends relationship (belongsToMany) by multiple columns

You can add additional conditions when you're declaring relationship by simply chaining it.

<?php
//...
class User extends Model {
//...
public function friends() {
return $this->hasMany(/*...*/)->orWhere('uid2', $this->id);
}
//...

But keep in mind that eloquent is not grouping the first conditions of relation in parenthesis so you might end with SQL that will not work as expected in some cases (if using or, and should be fine)

For example the above might result in a SQL that looks like this

SELECT * FROM users_friends WHERE uid1 = ? AND uid1 IS NOT NULL OR uid2 = ?

Which is a correct SQL statement but without grouping you will not get the result that you're expecting.

Another way is to use accessor and two separate relationships

<?php
//...
public function friends1() {
return $this->belongsToMany(User::class, 'users_friends', 'uid1');
}

public function friends2() {
return $this->belongsToMany(User::class, 'users_friends', 'uid2');
}

public function getFriendsAttribute() {
return $this->friends1->merge($this->friends2);
}
//...

But this way you get two separate trips to DB.

Laravel - Friendship

That's a common mistake. Beware of your "orWhere" clause.

public function isFriend($slug)
{
// Get both user
$user = Auth::user();
$receiver = User::where('slug', $slug)->first();
// get list of friends (so who have status = 1)

$result = Friends::where('status',1)->where(function($query) use ($receiver,$user)
{
$query->where([
'user_id' => $user->id,
'friend_id' => $receiver_id
])->orWhere([
'user_id' => $receiver->id,
'friend_id' => $user->id
]);

})->get();

return ! $result->isEmpty();
}


Related Topics



Leave a reply



Submit