Issues (10)

src/Followable.php (3 issues)

1
<?php
2
3
namespace Overtrue\LaravelFollow;
4
5
use Illuminate\Contracts\Pagination\Paginator;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Pagination\LengthAwarePaginator;
8
use Illuminate\Support\Collection;
9
use Overtrue\LaravelFavorite\Traits\Favoriteable;
10
11
/**
12
 * @property \Illuminate\Database\Eloquent\Collection $followings
13
 * @property \Illuminate\Database\Eloquent\Collection $followers
14
 */
15
trait Followable
16
{
17
    /**
18
     * @return bool
19
     */
20
    public function needsToApproveFollowRequests(): bool
21
    {
22
        return false;
23
    }
24
25
    /**
26
     * @param \Illuminate\Database\Eloquent\Model|int $user
27
     *
28
     * @return array
29
     */
30
    public function follow($user): array
31
    {
32
        $isPending = $user->needsToApproveFollowRequests() ?: false;
33
34
        $this->followings()->attach($user, [
35
            'accepted_at' => $isPending ? null : now()
36
        ]);
37
38
        return ['pending' => $isPending];
39
    }
40
41
    /**
42
     * @param \Illuminate\Database\Eloquent\Model|int $user
43
     */
44
    public function unfollow($user)
45
    {
46
        $this->followings()->detach($user);
47
    }
48
49
    /**
50
     * @param \Illuminate\Database\Eloquent\Model|int $user
51
     *
52
     */
53
    public function toggleFollow($user)
54
    {
55
        $this->isFollowing($user) ? $this->unfollow($user) : $this->follow($user);
56
    }
57
58
    /**
59
     * @param \Illuminate\Database\Eloquent\Model|int $user
60
     */
61
    public function rejectFollowRequestFrom($user)
62
    {
63
        $this->followers()->detach($user);
64
    }
65
66
    /**
67
     * @param \Illuminate\Database\Eloquent\Model|int $user
68
     */
69
    public function acceptFollowRequestFrom($user)
70
    {
71
        $this->followers()->updateExistingPivot($user, ['accepted_at' => now()]);
72
    }
73
74
    /**
75
     * @param \Illuminate\Database\Eloquent\Model|int $user
76
     */
77
    public function hasRequestedToFollow($user): bool
78
    {
79
        if ($user instanceof Model) {
80
            $user = $user->getKey();
81
        }
82
83
        /* @var \Illuminate\Database\Eloquent\Model $this */
84
        if ($this->relationLoaded('followings')) {
85
            return $this->followings
86
                ->where('pivot.accepted_at', '===', null)
87
                ->contains($user);
88
        }
89
90
        return $this->followings()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->followings...ame(), $user)->exists() could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
91
            ->wherePivot('accepted_at', null)
92
            ->where($this->getQualifiedKeyName(), $user)
93
            ->exists();
94
    }
95
96
    /**
97
     * @param \Illuminate\Database\Eloquent\Model|int $user
98
     */
99
    public function isFollowing($user): bool
100
    {
101
        if ($user instanceof Model) {
102
            $user = $user->getKey();
103
        }
104
105
        /* @var \Illuminate\Database\Eloquent\Model $this */
106
        if ($this->relationLoaded('followings')) {
107
            return $this->followings
108
                ->where('pivot.accepted_at', '!==', null)
109
                ->contains($user);
110
        }
111
112
        return $this->followings()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->followings...ame(), $user)->exists() could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
113
            ->wherePivot('accepted_at', '!=', null)
114
            ->where($this->getQualifiedKeyName(), $user)
115
            ->exists();
116
    }
117
118
    /**
119
     * @param \Illuminate\Database\Eloquent\Model|int $user
120
     */
121
    public function isFollowedBy($user): bool
122
    {
123
        if ($user instanceof Model) {
124
            $user = $user->getKey();
125
        }
126
127
        /* @var \Illuminate\Database\Eloquent\Model $this */
128
        if ($this->relationLoaded('followers')) {
129
            return $this->followers
130
                ->where('pivot.accepted_at', '!==', null)
131
                ->contains($user);
132
        }
133
134
        return $this->followers()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->followers(...ame(), $user)->exists() could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
135
            ->wherePivot('accepted_at', '!=', null)
136
            ->where($this->getQualifiedKeyName(), $user)
137
            ->exists();
138
    }
139
140
    /**
141
     * @param \Illuminate\Database\Eloquent\Model|int $user
142
     */
143
    public function areFollowingEachOther($user): bool
144
    {
145
        /* @var \Illuminate\Database\Eloquent\Model $user*/
146
        return $this->isFollowing($user) && $this->isFollowedBy($user);
147
    }
148
149
    public function scopeOrderByFollowersCount($query, string $direction = 'desc')
150
    {
151
        return $query->withCount('followers')->orderBy('followers_count', $direction);
152
    }
153
154
    public function scopeOrderByFollowersCountDesc($query)
155
    {
156
        return $this->scopeOrderByFollowersCount($query, 'desc');
157
    }
158
159
    public function scopeOrderByFollowersCountAsc($query)
160
    {
161
        return $this->scopeOrderByFollowersCount($query, 'asc');
162
    }
163
164
    public function followers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
165
    {
166
        /* @var \Illuminate\Database\Eloquent\Model $this */
167
        return $this->belongsToMany(
168
            __CLASS__,
169
            \config('follow.relation_table', 'user_follower'),
170
            'following_id',
171
            'follower_id'
172
        )->withPivot('accepted_at')->withTimestamps()->using(UserFollower::class);
173
    }
174
175
    public function followings(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
176
    {
177
        /* @var \Illuminate\Database\Eloquent\Model $this */
178
        return $this->belongsToMany(
179
            __CLASS__,
180
            \config('follow.relation_table', 'user_follower'),
181
            'follower_id',
182
            'following_id'
183
        )->withPivot('accepted_at')->withTimestamps()->using(UserFollower::class);
184
    }
185
186
    public function attachFollowStatus($followables, callable $resolver = null)
187
    {
188
        $returnFirst = false;
189
190
        switch (true) {
191
            case $followables instanceof Model:
192
                $returnFirst = true;
193
                $followables = \collect([$followables]);
194
                break;
195
            case $followables instanceof LengthAwarePaginator:
196
                $followables = $followables->getCollection();
197
                break;
198
            case $followables instanceof Paginator:
199
                $followables = \collect($followables->items());
200
                break;
201
            case \is_array($followables):
202
                $followables = \collect($followables);
203
                break;
204
        }
205
206
        \abort_if(!($followables instanceof Collection), 422, 'Invalid $followables type.');
207
208
        $followed = UserFollower::where('follower_id', $this->getKey())->get();
209
210
        $followables->map(function ($followable) use ($followed, $resolver) {
211
            $resolver = $resolver ?? fn ($m) => $m;
212
            $followable = $resolver($followable);
213
214
            if ($followable && \in_array(Followable::class, \class_uses($followable))) {
215
                $item = $followed->where('following_id', $followable->getKey())->first();
216
                $followable->setAttribute('has_followed', !!$item);
217
                $followable->setAttribute('followed_at', $item ? $item->created_at : null);
218
                $followable->setAttribute('follow_accepted_at', $item ? $item->accepted_at : null);
219
            }
220
        });
221
222
        return $returnFirst ? $followables->first() : $followables;
223
    }
224
}
225