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
Resize Images in PHP Without Using Third-Party Libraries
How to Read Png Metadata from PHP
Joomla 3.2.1 Password Encryption
How Does the "&" Operator Work in a PHP Function
Select Entries Between Dates in Doctrine 2
Laravel Preg_Match(): No Ending Delimiter '/' Found
Removing Array Index Reference When Using JSON_Encode()
Setting the Timezone for PHP in the PHP.Ini File
How Do Detect That Transaction Has Already Been Started
File_Get_Contents() Give Me 403 Forbidden
How to Extract Images from a PDF File
Understanding Nested PHP Ternary Operator
Close Open HTML Tags in a String