LikeableService::incrementLikesCount()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 12
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Turahe\Likeable\Services;
4
5
use Illuminate\Support\Facades\DB;
6
use Turahe\Likeable\Enums\LikeType;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Query\JoinClause;
9
use Turahe\Likeable\Contracts\Like as LikeContract;
10
use Turahe\Likeable\Exceptions\LikerNotDefinedException;
11
use Turahe\Likeable\Exceptions\LikeTypeInvalidException;
12
use Turahe\Likeable\Contracts\Likeable as LikeableContract;
13
use Turahe\Likeable\Contracts\LikeCounter as LikeCounterContract;
14
use Turahe\Likeable\Contracts\LikeableService as LikeableServiceContract;
15
16
class LikeableService implements LikeableServiceContract
17
{
18
    /**
19
     * Add a like to likeable model by user.
20
     *
21
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
22
     * @param string $type
23
     * @param string $userId
24
     * @return void
25
     *
26
     * @throws \Turahe\Likeable\Exceptions\LikerNotDefinedException
27
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
28
     */
29
    public function addLikeTo(LikeableContract $likeable, $type, $userId)
30
    {
31
        $userId = $this->getLikerUserId($userId);
0 ignored issues
show
Bug introduced by
$userId of type string is incompatible with the type integer expected by parameter $userId of Turahe\Likeable\Services...rvice::getLikerUserId(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

31
        $userId = $this->getLikerUserId(/** @scrutinizer ignore-type */ $userId);
Loading history...
32
33
        $like = $likeable->likesAndDislikes()->where([
34
            'user_id' => $userId,
35
        ])->first();
36
37
        if (! $like) {
38
            $likeable->likes()->create([
39
                'user_id' => $userId,
40
                'type_id' => $this->getLikeTypeId($type),
41
            ]);
42
43
            return;
44
        }
45
46
        if ($like->type_id == $this->getLikeTypeId($type)) {
47
            return;
48
        }
49
50
        $like->delete();
51
52
        $likeable->likes()->create([
53
            'user_id' => $userId,
54
            'type_id' => $this->getLikeTypeId($type),
55
        ]);
56
    }
57
58
    /**
59
     * Remove a like to likeable model by user.
60
     *
61
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
62
     * @param string $type
63
     * @param int|null $userId
64
     * @return void
65
     *
66
     * @throws \Turahe\Likeable\Exceptions\LikerNotDefinedException
67
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
68
     */
69
    public function removeLikeFrom(LikeableContract $likeable, $type, $userId)
70
    {
71
        $like = $likeable->likesAndDislikes()->where([
72
            'user_id' => $this->getLikerUserId($userId),
73
            'type_id' => $this->getLikeTypeId($type),
74
        ])->first();
75
76
        if (! $like) {
77
            return;
78
        }
79
80
        $like->delete();
81
    }
82
83
    /**
84
     * Toggle like for model by the given user.
85
     *
86
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
87
     * @param string $type
88
     * @param string $userId
89
     * @return void
90
     *
91
     * @throws \Turahe\Likeable\Exceptions\LikerNotDefinedException
92
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
93
     */
94
    public function toggleLikeOf(LikeableContract $likeable, $type, $userId)
95
    {
96
        $userId = $this->getLikerUserId($userId);
0 ignored issues
show
Bug introduced by
$userId of type string is incompatible with the type integer expected by parameter $userId of Turahe\Likeable\Services...rvice::getLikerUserId(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
        $userId = $this->getLikerUserId(/** @scrutinizer ignore-type */ $userId);
Loading history...
97
98
        $like = $likeable->likesAndDislikes()->where([
99
            'user_id' => $userId,
100
            'type_id' => $this->getLikeTypeId($type),
101
        ])->exists();
102
103
        if ($like) {
104
            $this->removeLikeFrom($likeable, $type, $userId);
105
        } else {
106
            $this->addLikeTo($likeable, $type, $userId);
107
        }
108
    }
109
110
    /**
111
     * Has the user already liked likeable model.
112
     *
113
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
114
     * @param string $type
115
     * @param int|null $userId
116
     * @return bool
117
     *
118
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
119
     */
120
    public function isLiked(LikeableContract $likeable, $type, $userId)
121
    {
122
        if (is_null($userId)) {
123
            $userId = $this->loggedInUserId();
124
        }
125
126
        if (! $userId) {
127
            return false;
128
        }
129
130
        $typeId = $this->getLikeTypeId($type);
131
132
        $exists = $this->hasLikeOrDislikeInLoadedRelation($likeable, $typeId, $userId);
133
        if (! is_null($exists)) {
134
            return $exists;
135
        }
136
137
        return $likeable->likesAndDislikes()->where([
138
            'user_id' => $userId,
139
            'type_id' => $typeId,
140
        ])->exists();
141
    }
142
143
    /**
144
     * Decrement the total like count stored in the counter.
145
     *
146
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
147
     * @return void
148
     */
149
    public function decrementLikesCount(LikeableContract $likeable)
150
    {
151
        $counter = $likeable->likesCounter()->first();
152
153
        if (! $counter) {
154
            return;
155
        }
156
157
        $counter->decrement('count');
158
    }
159
160
    /**
161
     * Increment the total like count stored in the counter.
162
     *
163
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
164
     * @return void
165
     */
166
    public function incrementLikesCount(LikeableContract $likeable)
167
    {
168
        $counter = $likeable->likesCounter()->first();
169
170
        if (! $counter) {
171
            $counter = $likeable->likesCounter()->create([
172
                'count' => 0,
173
                'type_id' => LikeType::LIKE,
174
            ]);
175
        }
176
177
        $counter->increment('count');
178
    }
179
180
    /**
181
     * Decrement the total dislike count stored in the counter.
182
     *
183
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
184
     * @return void
185
     */
186
    public function decrementDislikesCount(LikeableContract $likeable)
187
    {
188
        $counter = $likeable->dislikesCounter()->first();
189
190
        if (! $counter) {
191
            return;
192
        }
193
194
        $counter->decrement('count');
195
    }
196
197
    /**
198
     * Increment the total dislike count stored in the counter.
199
     *
200
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
201
     * @return void
202
     */
203
    public function incrementDislikesCount(LikeableContract $likeable)
204
    {
205
        $counter = $likeable->dislikesCounter()->first();
206
207
        if (! $counter) {
208
            $counter = $likeable->dislikesCounter()->create([
209
                'count' => 0,
210
                'type_id' => LikeType::DISLIKE,
211
            ]);
212
        }
213
214
        $counter->increment('count');
215
    }
216
217
    /**
218
     * Remove like counters by likeable type.
219
     *
220
     * @param string $likeableType
221
     * @param string|null $type
222
     * @return void
223
     *
224
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
225
     */
226
    public function removeLikeCountersOfType($likeableType, $type = null)
227
    {
228
        if (class_exists($likeableType)) {
229
            /** @var \Turahe\Likeable\Contracts\Likeable $likeable */
230
            $likeable = new $likeableType;
231
            $likeableType = $likeable->getMorphClass();
232
        }
233
234
        /** @var \Illuminate\Database\Eloquent\Builder $counters */
235
        $counters = app(LikeCounterContract::class)->where('likeable_type', $likeableType);
0 ignored issues
show
Bug introduced by
The method where() does not exist on Turahe\Likeable\Contracts\LikeCounter. Since it exists in all sub-types, consider adding an abstract or default implementation to Turahe\Likeable\Contracts\LikeCounter. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

235
        $counters = app(LikeCounterContract::class)->/** @scrutinizer ignore-call */ where('likeable_type', $likeableType);
Loading history...
236
        if (! is_null($type)) {
237
            $counters->where('type_id', $this->getLikeTypeId($type));
238
        }
239
        $counters->delete();
240
    }
241
242
    /**
243
     * Remove all likes from likeable model.
244
     *
245
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
246
     * @param string $type
247
     * @return void
248
     *
249
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
250
     */
251
    public function removeModelLikes(LikeableContract $likeable, $type)
252
    {
253
        app(LikeContract::class)->where([
0 ignored issues
show
Bug introduced by
The method where() does not exist on Turahe\Likeable\Contracts\Like. Since it exists in all sub-types, consider adding an abstract or default implementation to Turahe\Likeable\Contracts\Like. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

253
        app(LikeContract::class)->/** @scrutinizer ignore-call */ where([
Loading history...
254
            'likeable_id' => $likeable->getKey(),
255
            'likeable_type' => $likeable->getMorphClass(),
256
            'type_id' => $this->getLikeTypeId($type),
257
        ])->delete();
258
259
        app(LikeCounterContract::class)->where([
260
            'likeable_id' => $likeable->getKey(),
261
            'likeable_type' => $likeable->getMorphClass(),
262
            'type_id' => $this->getLikeTypeId($type),
263
        ])->delete();
264
    }
265
266
    /**
267
     * Get collection of users who liked entity.
268
     *
269
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
270
     * @return \Illuminate\Support\Collection
271
     */
272
    public function collectLikersOf(LikeableContract $likeable)
273
    {
274
        $userModel = $this->resolveUserModel();
275
276
        $likersIds = $likeable->likes->pluck('user_id');
0 ignored issues
show
Bug introduced by
Accessing likes on the interface Turahe\Likeable\Contracts\Likeable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
277
278
        return $userModel::whereKey($likersIds)->get();
0 ignored issues
show
Bug introduced by
The method whereKey() does not exist on Illuminate\Contracts\Auth\Authenticatable. It seems like you code against a sub-type of Illuminate\Contracts\Auth\Authenticatable such as Illuminate\Foundation\Auth\User. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

278
        return $userModel::/** @scrutinizer ignore-call */ whereKey($likersIds)->get();
Loading history...
279
    }
280
281
    /**
282
     * Get collection of users who disliked entity.
283
     *
284
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
285
     * @return \Illuminate\Support\Collection
286
     */
287
    public function collectDislikersOf(LikeableContract $likeable)
288
    {
289
        $userModel = $this->resolveUserModel();
290
291
        $likersIds = $likeable->dislikes->pluck('user_id');
0 ignored issues
show
Bug introduced by
Accessing dislikes on the interface Turahe\Likeable\Contracts\Likeable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
292
293
        return $userModel::whereKey($likersIds)->get();
294
    }
295
296
    /**
297
     * Fetch records that are liked by a given user id.
298
     *
299
     * @param \Illuminate\Database\Eloquent\Builder $query
300
     * @param string $type
301
     * @param int|null $userId
302
     * @return \Illuminate\Database\Eloquent\Builder
303
     *
304
     * @throws \Turahe\Likeable\Exceptions\LikerNotDefinedException
305
     */
306
    public function scopeWhereLikedBy(Builder $query, $type, $userId)
307
    {
308
        $userId = $this->getLikerUserId($userId);
309
310
        return $query->whereHas('likesAndDislikes', function (Builder $innerQuery) use ($type, $userId) {
311
            $innerQuery->where('user_id', $userId);
312
            $innerQuery->where('type_id', $this->getLikeTypeId($type));
313
        });
314
    }
315
316
    /**
317
     * Fetch records sorted by likes count.
318
     *
319
     * @param \Illuminate\Database\Eloquent\Builder $query
320
     * @param string $likeType
321
     * @param string $direction
322
     * @return \Illuminate\Database\Eloquent\Builder
323
     */
324
    public function scopeOrderByLikesCount(Builder $query, $likeType, $direction = 'desc')
325
    {
326
        $likeable = $query->getModel();
327
328
        return $query
329
            ->select($likeable->getTable().'.*', 'like_counter.count')
330
            ->leftJoin('like_counter', function (JoinClause $join) use ($likeable, $likeType) {
331
                $join
332
                    ->on('like_counter.likeable_id', '=', "{$likeable->getTable()}.{$likeable->getKeyName()}")
333
                    ->where('like_counter.likeable_type', '=', $likeable->getMorphClass())
334
                    ->where('like_counter.type_id', '=', $this->getLikeTypeId($likeType));
335
            })
336
            ->orderBy('like_counter.count', $direction);
337
    }
338
339
    /**
340
     * Fetch likes counters data.
341
     *
342
     * @param string $likeableType
343
     * @param string $likeType
344
     * @return array
345
     *
346
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
347
     */
348
    public function fetchLikesCounters($likeableType, $likeType)
349
    {
350
        /** @var \Illuminate\Database\Eloquent\Builder $likesCount */
351
        $likesCount = app(LikeContract::class)
352
            ->select([
0 ignored issues
show
Bug introduced by
The method select() does not exist on Turahe\Likeable\Contracts\Like. Since it exists in all sub-types, consider adding an abstract or default implementation to Turahe\Likeable\Contracts\Like. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

352
            ->/** @scrutinizer ignore-call */ select([
Loading history...
353
                DB::raw('COUNT(*) AS count'),
354
                'likeable_type',
355
                'likeable_id',
356
                'type_id',
357
            ])
358
            ->where('likeable_type', $likeableType);
359
360
        if (! is_null($likeType)) {
0 ignored issues
show
introduced by
The condition is_null($likeType) is always false.
Loading history...
361
            $likesCount->where('type_id', $this->getLikeTypeId($likeType));
362
        }
363
364
        $likesCount->groupBy('likeable_id', 'type_id');
365
366
        return $likesCount->get()->toArray();
367
    }
368
369
    /**
370
     * Get current user id or get user id passed in.
371
     *
372
     * @param int $userId
373
     * @return int
374
     *
375
     * @throws \Turahe\Likeable\Exceptions\LikerNotDefinedException
376
     */
377
    protected function getLikerUserId($userId)
378
    {
379
        if (is_null($userId)) {
0 ignored issues
show
introduced by
The condition is_null($userId) is always false.
Loading history...
380
            $userId = $this->loggedInUserId();
381
        }
382
383
        if (! $userId) {
384
            throw new LikerNotDefinedException();
385
        }
386
387
        return $userId;
388
    }
389
390
    /**
391
     * Fetch the primary ID of the currently logged in user.
392
     *
393
     * @return int
394
     */
395
    protected function loggedInUserId()
396
    {
397
        return auth()->id();
398
    }
399
400
    /**
401
     * Get like type id from name.
402
     *
403
     * @todo move to Enum class
404
     * @param string $type
405
     * @return int
406
     *
407
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
408
     */
409
    protected function getLikeTypeId($type)
410
    {
411
        $type = strtoupper($type);
412
        if (! defined("\\Turahe\\Likeable\\Enums\\LikeType::{$type}")) {
413
            throw new LikeTypeInvalidException("Like type `{$type}` not exist");
414
        }
415
416
        return constant("\\Turahe\\Likeable\\Enums\\LikeType::{$type}");
417
    }
418
419
    /**
420
     * Retrieve User's model class name.
421
     *
422
     * @return \Illuminate\Contracts\Auth\Authenticatable
423
     */
424
    private function resolveUserModel()
425
    {
426
        return config('auth.providers.users.model');
427
    }
428
429
    /**
430
     * @param \Turahe\Likeable\Contracts\Likeable $likeable
431
     * @param string $typeId
432
     * @param int $userId
433
     * @return bool|null
434
     *
435
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
436
     */
437
    private function hasLikeOrDislikeInLoadedRelation(LikeableContract $likeable, $typeId, $userId)
438
    {
439
        $relations = $this->likeTypeRelations($typeId);
440
441
        foreach ($relations as $relation) {
442
            if (! $likeable->relationLoaded($relation)) {
0 ignored issues
show
Bug introduced by
The method relationLoaded() does not exist on Turahe\Likeable\Contracts\Likeable. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

442
            if (! $likeable->/** @scrutinizer ignore-call */ relationLoaded($relation)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
443
                continue;
444
            }
445
446
            return $likeable->{$relation}->contains(function ($item) use ($userId, $typeId) {
447
                return $item->user_id == $userId && $item->type_id === $typeId;
448
            });
449
        }
450
451
        return null;
452
    }
453
454
    /**
455
     * Resolve list of likeable relations by like type.
456
     *
457
     * @param string $type
458
     * @return array
459
     *
460
     * @throws \Turahe\Likeable\Exceptions\LikeTypeInvalidException
461
     */
462
    private function likeTypeRelations($type)
463
    {
464
        $relations = [
465
            LikeType::LIKE => [
466
                'likes',
467
                'likesAndDislikes',
468
            ],
469
            LikeType::DISLIKE => [
470
                'dislikes',
471
                'likesAndDislikes',
472
            ],
473
        ];
474
475
        if (! isset($relations[$type])) {
476
            throw new LikeTypeInvalidException("Like type `{$type}` not supported");
477
        }
478
479
        return $relations[$type];
480
    }
481
}
482