Search code examples
laravelmany-to-many

Laravel belongsToMany attach() method not working after using contains method on the same relationship


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:

  1. Friends that has been confirmed, and this category including friends that I invite and friends who invite me;
  2. Invitees: those who I invite but not approved;
  3. Invitors: those who invite me but I do not say yes yet.

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!!!


Solution

  • 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);
       }