I am working on a friendship implementation in my project, but in addition to only adding friends, I would like to have invitation function just like facebook, I use a many to many self referencing approach,
pivot table schema:
Schema::create('user_friends', function (Blueprint $table) {
$table->primary(['user_id', 'friend_id']);
$table->unsignedInteger('user_id');
$table->unsignedInteger('friend_id');
$table->timestamps();
$table->timestamp('accepted_at')->nullable()->default(null);
$table->timestamp('refused_at')->nullable()->default(null);
$table->foreign('user_id')
->references('id')->on('users')
->onDelete('cascade');
$table->foreign('friend_id')
->references('id')->on('users')
->onDelete('cascade');
});
I divide the friends / to-be friends into 3 group:
Belows are their corresponding code:
Group 1: friends:
class User {
//
/**
* Friendships that started by the user
*/
public function friendsOfMine()
{
return $this->belongsToMany('App\User', 'user_friends', 'user_id', 'friend_id')
->withPivot(['accepted_at', 'refused_at'])
->wherePivot('accepted_at', '<>', null)
->withTimestamps();
}
/**
* Friendships that started by others
*/
public function friendOfOthers()
{
return $this->belongsToMany('App\User', 'user_friends', 'friend_id', 'user_id')
->withPivot(['accepted_at', 'refused_at'])
->wherePivot('accepted_at', '<>', null)
->withTimestamps();
}
// 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->friendOfOthers);
}
Group 2: Invitees:
/**
* Users that are invited as friends by this user.
*/
public function invitees()
{
return $this->belongsToMany('App\User', 'user_friends', 'user_id', 'friend_id')
->withPivot(['accepted_at', 'refused_at'])
->wherePivot('accepted_at', null)
->withTimestamps();
}
Group 3: Invitors:
/**
* Users that invite this user as friend.
*/
public function invitors()
{
return $this->belongsToMany('App\User', 'user_friends', 'friend_id', 'user_id')
->withPivot(['accepted_at', 'refused_at'])
->wherePivot('accepted_at', null)
->withTimestamps();
}
and then when I want to invite a new user, I want to make sure this new user dosen't belong to these 3 group:
public function inviteFriend(User $user)
{
if (!$this->is($user) &&
!$this->friends->contains($user) &&
!$this->invitees->contains($user) &&
!$this->invitors->contains($user)) {
$this->invitees()->attach($user);
}
}
Then here comes a strange problem, I used the dd() to inspect the progress and confirm that the condition is true and
$this->invitees()->attach($user);
has been executed, but however, the $user has not been added correctly, but if I remove this very one condition checking (not other one)
!$this->invitees->contains($user)
and the function becomes
public function inviteFriend(User $user)
{
if (!$this->is($user) &&
!$this->friends->contains($user) &&
// !$this->invitees->contains($user) &&
!$this->invitors->contains($user)) {
$this->invitees()->attach($user);
}
}
Then it would work normally, but however, then it would be possible to invite the same person repeatedly and then cause a SQL Constraint violation.
Anyone could help me out of this? Thank you in advance!!!
After more searching, I finally found a workaround to solve my problem, and I think this approach may be better than my original one in terms of efficiency, since collection->contains()
seems to be an overkill for this kind of situation.
if (!$this->is($user) &&
!$this->friendsOfMine()->wherePivot('friend_id', $user->id)->exists() &&
!$this->friendOfOthers()->wherePivot('user_id', $user->id)->exists() &&
!$this->invitees()->wherePivot('friend_id', $user->id)->exists() &&
!$this->invitors()->wherePivot('user_id', $user->id)->exists()) {
$this->invitees()->attach($user);
}