├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── database └── migrations │ ├── 2016_09_02_153301_create_like_table.php │ └── 2016_09_02_163301_create_like_counter_table.php └── src ├── Console └── LikeableRecountCommand.php ├── Contracts ├── HasLikes.php ├── Like.php ├── LikeCounter.php ├── Likeable.php └── LikeableService.php ├── Enums └── LikeType.php ├── Events ├── ModelWasDisliked.php ├── ModelWasLiked.php ├── ModelWasUndisliked.php └── ModelWasUnliked.php ├── Exceptions ├── LikeTypeInvalidException.php ├── LikerNotDefinedException.php └── ModelInvalidException.php ├── Models ├── Like.php └── LikeCounter.php ├── Observers ├── LikeObserver.php └── ModelObserver.php ├── Providers └── LikeableServiceProvider.php ├── Services └── LikeableService.php └── Traits ├── HasLikes.php └── Likeable.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-likeable` will be documented in this file. 4 | 5 | ## [3.1.0] - 2017-12-28 6 | 7 | ### Changed 8 | 9 | - Checks if model liked by user will try to search in eager loaded relations first 10 | 11 | ## [3.0.0] - 2017-08-24 12 | 13 | ### Added 14 | 15 | - Laravel 5.5 support 16 | - Laravel Package Auto-Discovery support 17 | - Eloquent related method `getKey` & `getMorphClass` added to `Cog\Likeable\Contracts\Likeable` contract 18 | - `collectLikers`, `collectDislikers` & `scopeOrderByDislikesCount` methods added to `Cog\Likeable\Contracts\Likeable` contract 19 | - `collectLikersOf` & `collectDislikersOf` methods to `Cog\Likeable\Contracts\LikeableService` contract 20 | 21 | ### Changed 22 | 23 | - `Cog\Likeable\Contracts` contract renamed to `Cog\Likeable\Contracts\Likeable` 24 | - `Cog\Likeable\Traits\HasLikes` trait renamed to `Cog\Likeable\Traits\Likeable` 25 | 26 | ## [2.2.5] - 2017-07-10 27 | 28 | ### Fixed 29 | 30 | - Event observing of custom `Like` model (#18) 31 | 32 | ## [2.2.4] - 2017-04-20 33 | 34 | ### Added 35 | 36 | - `orderByDislikesCount` scope added to `HasLikes` trait. 37 | - `scopeOrderByLikesCount` method to `LikeableService`. 38 | 39 | ### Fixed 40 | 41 | - `orderByLikesCount` count only likes now. 42 | 43 | ## [2.2.3] - 2017-04-20 44 | 45 | ### Fixed 46 | 47 | `orderByLikesCount` work in MySQL databases. 48 | 49 | ## [2.2.2] - 2017-04-09 50 | 51 | ### Fixed 52 | 53 | - `orderByLikesCount` returns models without likes too. 54 | 55 | ## [2.2.1] - 2017-04-09 56 | 57 | ### Fixed 58 | 59 | - `orderByLikesCount` database query fixed. 60 | 61 | ## [2.2.0] - 2017-04-09 62 | 63 | ### Added 64 | 65 | - `Article::orderByLikesCount('asc')` scope for the model. Uses `desc` as default order direction. 66 | 67 | ## [2.1.0] - 2017-02-20 68 | 69 | ### Added 70 | 71 | - Laravel 5.4 support. 72 | 73 | ## [2.0.1] - 2017-01-11 74 | 75 | - Removed unused properties in `LikeObserver` (#12) 76 | - Foreign key in migration commented out (#11) 77 | 78 | ## [2.0.0] - 2016-09-11 79 | 80 | - Renamed `FollowableService` methods to follow code style consistency: 81 | - `incrementLikeCount()` to `incrementLikesCount()` 82 | - `decrementLikeCount()` to `decrementLikesCount()` 83 | - `decrementDislikeCount()` to `decrementDislikesCount()` 84 | - `incrementDislikeCount()` to `incrementDislikesCount()` 85 | 86 | ## [1.1.2] - 2016-09-07 87 | 88 | - Fix enum like types 89 | 90 | ## [1.1.1] - 2016-09-07 91 | 92 | - Fix likeable enums database default value 93 | 94 | ## [1.1.0] - 2016-09-07 95 | 96 | - Renamed `HasLikes` trait methods to follow code style consistency: 97 | - `likeCounter()` to `likesCounter()` 98 | - `dislikeCounter()` to `dislikesCounter()` 99 | 100 | ## 1.0.0 - 2016-09-06 101 | 102 | - Initial release 103 | 104 | [3.1.0]: https://github.com/cybercog/laravel-likeable/compare/3.0.0...3.1.0 105 | [3.0.0]: https://github.com/cybercog/laravel-likeable/compare/2.2.5...3.0.0 106 | [2.2.5]: https://github.com/cybercog/laravel-likeable/compare/2.2.4...2.2.5 107 | [2.2.4]: https://github.com/cybercog/laravel-likeable/compare/2.2.3...2.2.4 108 | [2.2.3]: https://github.com/cybercog/laravel-likeable/compare/2.2.2...2.2.3 109 | [2.2.2]: https://github.com/cybercog/laravel-likeable/compare/2.2.1...2.2.2 110 | [2.2.1]: https://github.com/cybercog/laravel-likeable/compare/2.2.0...2.2.1 111 | [2.2.0]: https://github.com/cybercog/laravel-likeable/compare/2.1.0...2.2.0 112 | [2.1.0]: https://github.com/cybercog/laravel-likeable/compare/2.0.1...2.1.0 113 | [2.0.1]: https://github.com/cybercog/laravel-likeable/compare/2.0.0...2.0.1 114 | [2.0.0]: https://github.com/cybercog/laravel-likeable/compare/1.1.2...2.0.0 115 | [1.1.2]: https://github.com/cybercog/laravel-likeable/compare/1.1.1...1.1.2 116 | [1.1.1]: https://github.com/cybercog/laravel-likeable/compare/1.1.0...1.1.1 117 | [1.1.0]: https://github.com/cybercog/laravel-likeable/compare/1.0.0...1.1.0 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017, Anton Komarev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Likeable 2 | 3 | ![cog-laravel-likeable](https://user-images.githubusercontent.com/1849174/28696355-c4a06a96-733d-11e7-8cc5-af5d60bf5e20.png) 4 | 5 |

6 | Build Status 7 | StyleCI 8 | Releases 9 | License 10 |

11 | 12 | ## Attention 13 | 14 | **This package is abandoned and no longer maintained. 15 | Development moved to [Laravel Love package](https://github.com/cybercog/laravel-love)!** 16 | 17 | If you already have installed version of Laravel Likeable you can use [Laravel Love Migration Guide](https://github.com/cybercog/laravel-love/blob/master/UPGRADING.md#from-v3-to-v4). 18 | 19 | ## Introduction 20 | 21 | Laravel Likeable simplify management of Eloquent model's likes & dislikes. Make any model `likeable` & `dislikeable` in a minutes! 22 | 23 | ## Contents 24 | 25 | - [Features](#features) 26 | - [Installation](#installation) 27 | - [Usage](#usage) 28 | - [Prepare likeable model](#prepare-likeable-model) 29 | - [Available methods](#available-methods) 30 | - [Scopes](#scopes) 31 | - [Events](#events) 32 | - [Console commands](#console-commands) 33 | - [Extending](#extending) 34 | - [Change log](#change-log) 35 | - [Contributing](#contributing) 36 | - [Testing](#testing) 37 | - [Security](#security) 38 | - [Contributors](#contributors) 39 | - [Alternatives](#alternatives) 40 | - [License](#license) 41 | - [About CyberCog](#about-cybercog) 42 | 43 | ## Features 44 | 45 | - Designed to work with Laravel Eloquent models. 46 | - Using contracts to keep high customization capabilities. 47 | - Using traits to get functionality out of the box. 48 | - Most part of the the logic is handled by the `LikeableService`. 49 | - Has Artisan command `likeable:recount {model?} {type?}` to re-fetch likes counters. 50 | - Likeable model can has Likes and Dislikes. 51 | - Likes and Dislikes for one model are mutually exclusive. 52 | - Get Likeable models ordered by likes count. 53 | - Events for `like`, `unlike`, `dislike`, `undislike` methods. 54 | - Following PHP Standard Recommendations: 55 | - [PSR-1 (Basic Coding Standard)](http://www.php-fig.org/psr/psr-1/). 56 | - [PSR-2 (Coding Style Guide)](http://www.php-fig.org/psr/psr-2/). 57 | - [PSR-4 (Autoloading Standard)](http://www.php-fig.org/psr/psr-4/). 58 | - Covered with unit tests. 59 | 60 | ## Installation 61 | 62 | First, pull in the package through Composer. 63 | 64 | ```sh 65 | $ composer require cybercog/laravel-likeable 66 | ``` 67 | 68 | **If you are using Laravel 5.5 you can skip register package part.** 69 | 70 | #### Register package on Laravel 5.4 and lower 71 | 72 | Include the service provider within `app/config/app.php`. 73 | 74 | ```php 75 | 'providers' => [ 76 | Cog\Likeable\Providers\LikeableServiceProvider::class, 77 | ], 78 | ``` 79 | 80 | #### Perform Database Migration 81 | 82 | At last you need to publish and run database migrations. 83 | 84 | ```sh 85 | $ php artisan vendor:publish --provider="Cog\Likeable\Providers\LikeableServiceProvider" --tag=migrations 86 | $ php artisan migrate 87 | ``` 88 | 89 | ## Usage 90 | 91 | ### Prepare likeable model 92 | 93 | Use `Likeable` contract in model which will get likes behavior and implement it or just use `Likeable` trait. 94 | 95 | ```php 96 | use Cog\Likeable\Contracts\Likeable as LikeableContract; 97 | use Cog\Likeable\Traits\Likeable; 98 | use Illuminate\Database\Eloquent\Model; 99 | 100 | class Article extends Model implements LikeableContract 101 | { 102 | use Likeable; 103 | } 104 | ``` 105 | 106 | ### Available methods 107 | 108 | #### Likes 109 | 110 | ##### Like model 111 | 112 | ```php 113 | $article->like(); // current user 114 | $article->like($user->id); 115 | ``` 116 | 117 | ##### Remove like mark from model 118 | 119 | ```php 120 | $article->unlike(); // current user 121 | $article->unlike($user->id); 122 | ``` 123 | 124 | ##### Toggle like mark of model 125 | 126 | ```php 127 | $article->likeToggle(); // current user 128 | $article->likeToggle($user->id); 129 | ``` 130 | 131 | ##### Get model likes count 132 | 133 | ```php 134 | $article->likesCount; 135 | ``` 136 | 137 | ##### Get model likes counter 138 | 139 | ```php 140 | $article->likesCounter; 141 | ``` 142 | 143 | ##### Get likes relation 144 | 145 | ```php 146 | $article->likes(); 147 | ``` 148 | 149 | ##### Get iterable `Illuminate\Database\Eloquent\Collection` of existing model likes 150 | 151 | ```php 152 | $article->likes; 153 | ``` 154 | 155 | ##### Boolean check if user liked model 156 | 157 | ```php 158 | $article->liked; // current user 159 | $article->liked(); // current user 160 | $article->liked($user->id); 161 | ``` 162 | 163 | *Checks in eager loaded relations `likes` & `likesAndDislikes` first.* 164 | 165 | ##### Get collection of users who liked model 166 | 167 | ```php 168 | $article->collectLikers(); 169 | ``` 170 | 171 | ##### Delete all likes for model 172 | 173 | ```php 174 | $article->removeLikes(); 175 | ``` 176 | 177 | #### Dislikes 178 | 179 | ##### Dislike model 180 | 181 | ```php 182 | $article->dislike(); // current user 183 | $article->dislike($user->id); 184 | ``` 185 | 186 | ##### Remove dislike mark from model 187 | 188 | ```php 189 | $article->undislike(); // current user 190 | $article->undislike($user->id); 191 | ``` 192 | 193 | ##### Toggle dislike mark of model 194 | 195 | ```php 196 | $article->dislikeToggle(); // current user 197 | $article->dislikeToggle($user->id); 198 | ``` 199 | 200 | ##### Get model dislikes count 201 | 202 | ```php 203 | $article->dislikesCount; 204 | ``` 205 | 206 | ##### Get model dislikes counter 207 | 208 | ```php 209 | $article->dislikesCounter; 210 | ``` 211 | 212 | ##### Get dislikes relation 213 | 214 | ```php 215 | $article->dislikes(); 216 | ``` 217 | 218 | ##### Get iterable `Illuminate\Database\Eloquent\Collection` of existing model dislikes 219 | 220 | ```php 221 | $article->dislikes; 222 | ``` 223 | 224 | ##### Boolean check if user disliked model 225 | 226 | ```php 227 | $article->disliked; // current user 228 | $article->disliked(); // current user 229 | $article->disliked($user->id); 230 | ``` 231 | 232 | *Checks in eager loaded relations `dislikes` & `likesAndDislikes` first.* 233 | 234 | ##### Get collection of users who disliked model 235 | 236 | ```php 237 | $article->collectDislikers(); 238 | ``` 239 | 240 | ##### Delete all dislikes for model 241 | 242 | ```php 243 | $article->removeDislikes(); 244 | ``` 245 | 246 | #### Likes and Dislikes 247 | 248 | ##### Get difference between likes and dislikes 249 | 250 | ```php 251 | $article->likesDiffDislikesCount; 252 | ``` 253 | 254 | ##### Get likes and dislikes relation 255 | 256 | ```php 257 | $article->likesAndDislikes(); 258 | ``` 259 | 260 | ##### Get iterable `Illuminate\Database\Eloquent\Collection` of existing model likes and dislikes 261 | 262 | ```php 263 | $article->likesAndDislikes; 264 | ``` 265 | 266 | ### Scopes 267 | 268 | ##### Find all articles liked by user 269 | 270 | ```php 271 | Article::whereLikedBy($user->id) 272 | ->with('likesCounter') // Allow eager load (optional) 273 | ->get(); 274 | ``` 275 | 276 | ##### Find all articles disliked by user 277 | 278 | ```php 279 | Article::whereDislikedBy($user->id) 280 | ->with('dislikesCounter') // Allow eager load (optional) 281 | ->get(); 282 | ``` 283 | 284 | ##### Fetch Likeable models by likes count 285 | 286 | ```php 287 | $sortedArticles = Article::orderByLikesCount()->get(); 288 | $sortedArticles = Article::orderByLikesCount('asc')->get(); 289 | ``` 290 | 291 | *Uses `desc` as default order direction.* 292 | 293 | ##### Fetch Likeable models by dislikes count 294 | 295 | ```php 296 | $sortedArticles = Article::orderByDislikesCount()->get(); 297 | $sortedArticles = Article::orderByDislikesCount('asc')->get(); 298 | ``` 299 | 300 | *Uses `desc` as default order direction.* 301 | 302 | ### Events 303 | 304 | On each like added `\Cog\Likeable\Events\ModelWasLiked` event is fired. 305 | 306 | On each like removed `\Cog\Likeable\Events\ModelWasUnliked` event is fired. 307 | 308 | On each dislike added `\Cog\Likeable\Events\ModelWasDisliked` event is fired. 309 | 310 | On each dislike removed `\Cog\Likeable\Events\ModelWasUndisliked` event is fired. 311 | 312 | ### Console commands 313 | 314 | ##### Recount likes and dislikes of all model types 315 | 316 | ```sh 317 | $ likeable:recount 318 | ``` 319 | 320 | ##### Recount likes and dislikes of concrete model type (using morph map alias) 321 | 322 | ```sh 323 | $ likeable:recount --model="article" 324 | ``` 325 | 326 | ##### Recount likes and dislikes of concrete model type (using fully qualified class name) 327 | 328 | ```sh 329 | $ likeable:recount --model="App\Models\Article" 330 | ``` 331 | 332 | ##### Recount only likes of all model types 333 | 334 | ```sh 335 | $ likeable:recount --type="like" 336 | ``` 337 | 338 | ##### Recount only likes of concrete model type (using morph map alias) 339 | 340 | ```sh 341 | $ likeable:recount --model="article" --type="like" 342 | ``` 343 | 344 | ##### Recount only likes of concrete model type (using fully qualified class name) 345 | 346 | ```sh 347 | $ likeable:recount --model="App\Models\Article" --type="like" 348 | ``` 349 | 350 | ##### Recount only dislikes of all model types 351 | 352 | ```sh 353 | $ likeable:recount --type="dislike" 354 | ``` 355 | 356 | ##### Recount only dislikes of concrete model type (using morph map alias) 357 | 358 | ```sh 359 | $ likeable:recount --model="article" --type="dislike" 360 | ``` 361 | 362 | ##### Recount only dislikes of concrete model type (using fully qualified class name) 363 | 364 | ```sh 365 | $ likeable:recount --model="App\Models\Article" --type="dislike" 366 | ``` 367 | 368 | ## Extending 369 | 370 | You can override core classes of package with your own implementations: 371 | 372 | - `Models\Like` 373 | - `Models\LikeCounter` 374 | - `Services\LikeableService` 375 | 376 | *Note: Don't forget that all custom models must implement original models interfaces.* 377 | 378 | To make it you should use container [binding interfaces to implementations](https://laravel.com/docs/master/container#binding-interfaces-to-implementations) in your application service providers. 379 | 380 | ##### Use model class own implementation 381 | 382 | ```php 383 | $this->app->bind( 384 | \Cog\Likeable\Contracts\Like::class, 385 | \App\Models\CustomLike::class 386 | ); 387 | ``` 388 | 389 | ##### Use service class own implementation 390 | 391 | ```php 392 | $this->app->singleton( 393 | \Cog\Likeable\Contracts\LikeableService::class, 394 | \App\Services\CustomService::class 395 | ); 396 | ``` 397 | 398 | After that your `CustomLike` and `CustomService` classes will be instantiable with helper method `app()`. 399 | 400 | ```php 401 | $model = app(\Cog\Likeable\Contracts\Like::class); 402 | $service = app(\Cog\Likeable\Contracts\LikeableService::class); 403 | ``` 404 | 405 | ## Change log 406 | 407 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 408 | 409 | ## Contributing 410 | 411 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 412 | 413 | ## Testing 414 | 415 | You can run the tests with: 416 | 417 | ```sh 418 | $ vendor/bin/phpunit 419 | ``` 420 | 421 | ## Security 422 | 423 | If you discover any security related issues, please email open@cybercog.su instead of using the issue tracker. 424 | 425 | ## Contributors 426 | 427 | | ![@antonkomarev](https://avatars.githubusercontent.com/u/1849174?s=110)
Anton Komarev
| ![@acidjazz](https://avatars.githubusercontent.com/u/967369?s=110)
Kevin Olson
| 428 | | :---: | :---: | 429 | 430 | [Laravel Likeable contributors list](../../contributors) 431 | 432 | ## Alternatives 433 | 434 | - [cybercog/laravel-love](https://github.com/cybercog/laravel-love) 435 | - [rtconner/laravel-likeable](https://github.com/rtconner/laravel-likeable) 436 | - [faustbrian/laravel-likeable](https://github.com/faustbrian/Laravel-Likeable) 437 | - [sukohi/evaluation](https://github.com/SUKOHI/Evaluation) 438 | - [zvermafia/lavoter](https://github.com/zvermafia/lavoter) 439 | 440 | *Feel free to add more alternatives as Pull Request.* 441 | 442 | ## License 443 | 444 | - `Laravel Likeable` package is open-sourced software licensed under the [MIT license](LICENSE) by [Anton Komarev]. 445 | 446 | ## About CyberCog 447 | 448 | [CyberCog](https://cybercog.su) is a Social Unity of enthusiasts. Research best solutions in product & software development is our passion. 449 | 450 | - [Follow us on Twitter](https://twitter.com/cybercog) 451 | - [Read our articles on Medium](https://medium.com/cybercog) 452 | 453 | CyberCog 454 | 455 | [Anton Komarev]: https://komarev.com 456 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cybercog/laravel-likeable", 3 | "description": "Make Laravel Eloquent models Likeable & Dislikeable in a minutes!", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "cybercog", 8 | "cog", 9 | "laravel", 10 | "eloquent", 11 | "trait", 12 | "likeable", 13 | "likable", 14 | "like", 15 | "dislike", 16 | "favorite", 17 | "favourite", 18 | "rate", 19 | "rating", 20 | "emotion", 21 | "star", 22 | "kudos" 23 | ], 24 | "authors": [ 25 | { 26 | "name": "Anton Komarev", 27 | "email": "anton@komarev.com", 28 | "homepage": "https://komarev.com", 29 | "role": "Developer" 30 | } 31 | ], 32 | "homepage": "https://github.com/cybercog/laravel-likeable", 33 | "support": { 34 | "email": "open@cybercog.su", 35 | "issues": "https://github.com/cybercog/laravel-likeable/issues", 36 | "wiki": "https://github.com/cybercog/laravel-likeable/wiki", 37 | "source": "https://github.com/cybercog/laravel-likeable", 38 | "docs": "https://github.com/cybercog/laravel-likeable/wiki" 39 | }, 40 | "require": { 41 | "php": "^5.6|^7.0", 42 | "illuminate/database": "~5.1.20|~5.2|~5.3|~5.4|~5.5", 43 | "illuminate/support": "~5.1.20|~5.2|~5.3|~5.4|~5.5" 44 | }, 45 | "require-dev": { 46 | "friendsofphp/php-cs-fixer": "^1.11", 47 | "mockery/mockery": "^0.9.8", 48 | "orchestra/database": "~3.4.0|~3.5.0", 49 | "orchestra/testbench": "~3.4.0|~3.5.0", 50 | "phpunit/phpunit": "^5.7|^6.0" 51 | }, 52 | "autoload": { 53 | "psr-4": { 54 | "Cog\\Likeable\\": "src/" 55 | } 56 | }, 57 | "autoload-dev": { 58 | "psr-4": { 59 | "Cog\\Likeable\\Tests\\": "tests/" 60 | } 61 | }, 62 | "scripts": { 63 | "test": "vendor/bin/phpunit" 64 | }, 65 | "config": { 66 | "sort-packages": true 67 | }, 68 | "extra": { 69 | "laravel": { 70 | "providers": [ 71 | "Cog\\Likeable\\Providers\\LikeableServiceProvider" 72 | ] 73 | } 74 | }, 75 | "suggest": { 76 | "cybercog/laravel-love": "Completely refactored Laravel Likeable package." 77 | }, 78 | "minimum-stability": "dev", 79 | "prefer-stable" : true 80 | } 81 | -------------------------------------------------------------------------------- /database/migrations/2016_09_02_153301_create_like_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * Class CreateLikeTable. 18 | */ 19 | class CreateLikeTable extends Migration 20 | { 21 | /** 22 | * Run the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function up() 27 | { 28 | Schema::create('like', function (Blueprint $table) { 29 | $table->increments('id'); 30 | $table->morphs('likeable'); 31 | $table->integer('user_id')->unsigned()->index(); 32 | $table->enum('type_id', [ 33 | 'like', 34 | 'dislike', 35 | ])->default('like'); 36 | $table->timestamp('created_at')->nullable(); 37 | 38 | $table->unique([ 39 | 'likeable_id', 40 | 'likeable_type', 41 | 'user_id', 42 | ], 'like_user_unique'); 43 | 44 | // $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); 45 | }); 46 | } 47 | 48 | /** 49 | * Reverse the migrations. 50 | * 51 | * @return void 52 | */ 53 | public function down() 54 | { 55 | Schema::dropIfExists('like'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /database/migrations/2016_09_02_163301_create_like_counter_table.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Illuminate\Database\Migrations\Migration; 13 | use Illuminate\Database\Schema\Blueprint; 14 | use Illuminate\Support\Facades\Schema; 15 | 16 | /** 17 | * Class CreateLikeCounterTable. 18 | */ 19 | class CreateLikeCounterTable extends Migration 20 | { 21 | /** 22 | * Run the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function up() 27 | { 28 | Schema::create('like_counter', function (Blueprint $table) { 29 | $table->increments('id'); 30 | $table->morphs('likeable'); 31 | $table->enum('type_id', [ 32 | 'like', 33 | 'dislike', 34 | ])->default('like'); 35 | $table->integer('count')->unsigned()->default(0); 36 | 37 | $table->unique([ 38 | 'likeable_id', 39 | 'likeable_type', 40 | 'type_id', 41 | ], 'like_counter_unique'); 42 | }); 43 | } 44 | 45 | /** 46 | * Reverse the migrations. 47 | * 48 | * @return void 49 | */ 50 | public function down() 51 | { 52 | Schema::dropIfExists('like_counter'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Console/LikeableRecountCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Console; 13 | 14 | use Cog\Likeable\Contracts\Likeable as LikeableContract; 15 | use Cog\Likeable\Contracts\Like as LikeContract; 16 | use Cog\Likeable\Contracts\LikeCounter as LikeCounterContract; 17 | use Cog\Likeable\Exceptions\ModelInvalidException; 18 | use Cog\Likeable\Services\LikeableService as LikeableServiceContract; 19 | use Illuminate\Console\Command; 20 | use Illuminate\Contracts\Events\Dispatcher; 21 | use Illuminate\Database\Eloquent\Relations\Relation; 22 | use Illuminate\Support\Facades\DB; 23 | 24 | /** 25 | * Class LikeableRecountCommand. 26 | * 27 | * @package Cog\Likeable\Console 28 | */ 29 | class LikeableRecountCommand extends Command 30 | { 31 | /** 32 | * The name and signature of the console command. 33 | * 34 | * @var string 35 | */ 36 | protected $signature = 'likeable:recount {model?} {type?}'; 37 | 38 | /** 39 | * The console command description. 40 | * 41 | * @var string 42 | */ 43 | protected $description = 'Recount likes and dislikes for the models'; 44 | 45 | /** 46 | * Type of likes to be recounted. 47 | * 48 | * @var string|null 49 | */ 50 | protected $likeType; 51 | 52 | /** 53 | * Likeable service. 54 | * 55 | * @var \Cog\Likeable\Contracts\LikeableService 56 | */ 57 | protected $service; 58 | 59 | /** 60 | * Execute the console command. 61 | * 62 | * @param \Illuminate\Contracts\Events\Dispatcher $events 63 | * @return void 64 | * 65 | * @throws \Cog\Likeable\Exceptions\ModelInvalidException 66 | */ 67 | public function handle(Dispatcher $events) 68 | { 69 | $model = $this->argument('model'); 70 | $this->likeType = $this->argument('type'); 71 | $this->service = app(LikeableServiceContract::class); 72 | 73 | if (empty($model)) { 74 | $this->recountLikesOfAllModelTypes(); 75 | } else { 76 | $this->recountLikesOfModelType($model); 77 | } 78 | } 79 | 80 | /** 81 | * Recount likes of all model types. 82 | * 83 | * @return void 84 | * 85 | * @throws \Cog\Likeable\Exceptions\ModelInvalidException 86 | */ 87 | protected function recountLikesOfAllModelTypes() 88 | { 89 | $likeableTypes = app(LikeContract::class)->groupBy('likeable_type')->get(); 90 | foreach ($likeableTypes as $like) { 91 | $this->recountLikesOfModelType($like->likeable_type); 92 | } 93 | } 94 | 95 | /** 96 | * Recount likes of model type. 97 | * 98 | * @param string $modelType 99 | * @return void 100 | * 101 | * @throws \Cog\Likeable\Exceptions\ModelInvalidException 102 | */ 103 | protected function recountLikesOfModelType($modelType) 104 | { 105 | $modelType = $this->normalizeModelType($modelType); 106 | 107 | $counters = $this->service->fetchLikesCounters($modelType, $this->likeType); 108 | 109 | $this->service->removeLikeCountersOfType($modelType, $this->likeType); 110 | 111 | DB::table(app(LikeCounterContract::class)->getTable())->insert($counters); 112 | 113 | $this->info('All [' . $modelType . '] records likes has been recounted.'); 114 | } 115 | 116 | /** 117 | * Normalize likeable model type. 118 | * 119 | * @param string $modelType 120 | * @return string 121 | * 122 | * @throws \Cog\Likeable\Exceptions\ModelInvalidException 123 | */ 124 | protected function normalizeModelType($modelType) 125 | { 126 | $morphMap = Relation::morphMap(); 127 | 128 | if (class_exists($modelType)) { 129 | $model = new $modelType; 130 | $modelType = $model->getMorphClass(); 131 | } else { 132 | if (!isset($morphMap[$modelType])) { 133 | throw new ModelInvalidException("[$modelType] class and morph map are not found."); 134 | } 135 | 136 | $modelClass = $morphMap[$modelType]; 137 | $model = new $modelClass; 138 | } 139 | 140 | if (!$model instanceof LikeableContract) { 141 | throw new ModelInvalidException("[$modelType] not implements Likeable contract."); 142 | } 143 | 144 | return $modelType; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Contracts/HasLikes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Contracts; 13 | 14 | /** 15 | * Interface HasLikes. 16 | * 17 | * @deprecated 3.0 18 | * @see \Cog\Likeable\Contracts\Likeable 19 | * @package Cog\Likeable\Contracts 20 | */ 21 | interface HasLikes extends Likeable 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Contracts/Like.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Contracts; 13 | 14 | /** 15 | * Interface Like. 16 | * 17 | * @property \Cog\Likeable\Contracts\Likeable likeable 18 | * @property int type_id 19 | * @property int user_id 20 | * @package Cog\Likeable\Contract 21 | */ 22 | interface Like 23 | { 24 | /** 25 | * Likeable model relation. 26 | * 27 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 28 | */ 29 | public function likeable(); 30 | } 31 | -------------------------------------------------------------------------------- /src/Contracts/LikeCounter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Contracts; 13 | 14 | /** 15 | * Interface LikeCounter. 16 | * 17 | * @property int type_id 18 | * @property int count 19 | * @package Cog\Likeable\Contracts 20 | */ 21 | interface LikeCounter 22 | { 23 | /** 24 | * Likeable model relation. 25 | * 26 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 27 | */ 28 | public function likeable(); 29 | } 30 | -------------------------------------------------------------------------------- /src/Contracts/Likeable.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Contracts; 13 | 14 | use Illuminate\Database\Eloquent\Builder; 15 | 16 | /** 17 | * Interface Likeable. 18 | * 19 | * @package Cog\Likeable\Contracts 20 | */ 21 | interface Likeable 22 | { 23 | /** 24 | * Get the value of the model's primary key. 25 | * 26 | * @return mixed 27 | */ 28 | public function getKey(); 29 | 30 | /** 31 | * Get the class name for polymorphic relations. 32 | * 33 | * @return string 34 | */ 35 | public function getMorphClass(); 36 | 37 | /** 38 | * Collection of the likes on this record. 39 | * 40 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 41 | */ 42 | public function likesAndDislikes(); 43 | 44 | /** 45 | * Collection of the likes on this record. 46 | * 47 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 48 | */ 49 | public function likes(); 50 | 51 | /** 52 | * Collection of the dislikes on this record. 53 | * 54 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 55 | */ 56 | public function dislikes(); 57 | 58 | /** 59 | * Counter is a record that stores the total likes for the morphed record. 60 | * 61 | * @return \Illuminate\Database\Eloquent\Relations\MorphOne 62 | */ 63 | public function likesCounter(); 64 | 65 | /** 66 | * Counter is a record that stores the total dislikes for the morphed record. 67 | * 68 | * @return \Illuminate\Database\Eloquent\Relations\MorphOne 69 | */ 70 | public function dislikesCounter(); 71 | 72 | /** 73 | * Fetch users who liked entity. 74 | * 75 | * @return \Illuminate\Support\Collection 76 | */ 77 | public function collectLikers(); 78 | 79 | /** 80 | * Fetch users who disliked entity. 81 | * 82 | * @return \Illuminate\Support\Collection 83 | */ 84 | public function collectDislikers(); 85 | 86 | /** 87 | * Model likesCount attribute. 88 | * 89 | * @return int 90 | */ 91 | public function getLikesCountAttribute(); 92 | 93 | /** 94 | * Model dislikesCount attribute. 95 | * 96 | * @return int 97 | */ 98 | public function getDislikesCountAttribute(); 99 | 100 | /** 101 | * Did the currently logged in user like this model. 102 | * 103 | * @return bool 104 | */ 105 | public function getLikedAttribute(); 106 | 107 | /** 108 | * Did the currently logged in user dislike this model. 109 | * 110 | * @return bool 111 | */ 112 | public function getDislikedAttribute(); 113 | 114 | /** 115 | * Difference between likes and dislikes count. 116 | * 117 | * @return int 118 | */ 119 | public function getLikesDiffDislikesCountAttribute(); 120 | 121 | /** 122 | * Fetch records that are liked by a given user id. 123 | * 124 | * @param \Illuminate\Database\Eloquent\Builder $query 125 | * @param int|null $userId 126 | * @return \Illuminate\Database\Eloquent\Builder 127 | * 128 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 129 | */ 130 | public function scopeWhereLikedBy(Builder $query, $userId = null); 131 | 132 | /** 133 | * Fetch records that are disliked by a given user id. 134 | * 135 | * @param \Illuminate\Database\Eloquent\Builder $query 136 | * @param int|null $userId 137 | * @return \Illuminate\Database\Eloquent\Builder 138 | * 139 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 140 | */ 141 | public function scopeWhereDislikedBy(Builder $query, $userId = null); 142 | 143 | /** 144 | * Fetch records sorted by likes count. 145 | * 146 | * @param \Illuminate\Database\Eloquent\Builder $query 147 | * @param string $direction 148 | * @return \Illuminate\Database\Eloquent\Builder 149 | */ 150 | public function scopeOrderByLikesCount(Builder $query, $direction = 'desc'); 151 | 152 | /** 153 | * Fetch records sorted by likes count. 154 | * 155 | * @param \Illuminate\Database\Eloquent\Builder $query 156 | * @param string $direction 157 | * @return \Illuminate\Database\Eloquent\Builder 158 | */ 159 | public function scopeOrderByDislikesCount(Builder $query, $direction = 'desc'); 160 | 161 | /** 162 | * Add a like for model by the given user. 163 | * 164 | * @param mixed $userId If null will use currently logged in user. 165 | * @return void 166 | * 167 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 168 | */ 169 | public function like($userId = null); 170 | 171 | /** 172 | * Remove a like from this record for the given user. 173 | * 174 | * @param int|null $userId If null will use currently logged in user. 175 | * @return void 176 | * 177 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 178 | */ 179 | public function unlike($userId = null); 180 | 181 | /** 182 | * Toggle like for model by the given user. 183 | * 184 | * @param mixed $userId If null will use currently logged in user. 185 | * @return void 186 | * 187 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 188 | */ 189 | public function likeToggle($userId = null); 190 | 191 | /** 192 | * Has the user already liked likeable model. 193 | * 194 | * @param int|null $userId 195 | * @return bool 196 | */ 197 | public function liked($userId = null); 198 | 199 | /** 200 | * Delete likes related to the current record. 201 | * 202 | * @return void 203 | */ 204 | public function removeLikes(); 205 | 206 | /** 207 | * Add a dislike for model by the given user. 208 | * 209 | * @param mixed $userId If null will use currently logged in user. 210 | * @return void 211 | * 212 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 213 | */ 214 | public function dislike($userId = null); 215 | 216 | /** 217 | * Remove a dislike from this record for the given user. 218 | * 219 | * @param int|null $userId If null will use currently logged in user. 220 | * @return void 221 | * 222 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 223 | */ 224 | public function undislike($userId = null); 225 | 226 | /** 227 | * Toggle dislike for model by the given user. 228 | * 229 | * @param mixed $userId If null will use currently logged in user. 230 | * @return void 231 | * 232 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 233 | */ 234 | public function dislikeToggle($userId = null); 235 | 236 | /** 237 | * Has the user already disliked likeable model. 238 | * 239 | * @param int|null $userId 240 | * @return bool 241 | */ 242 | public function disliked($userId = null); 243 | 244 | /** 245 | * Delete dislikes related to the current record. 246 | * 247 | * @return void 248 | */ 249 | public function removeDislikes(); 250 | } 251 | -------------------------------------------------------------------------------- /src/Contracts/LikeableService.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Contracts; 13 | 14 | use Cog\Likeable\Contracts\Likeable as LikeableContract; 15 | use Illuminate\Database\Eloquent\Builder; 16 | 17 | /** 18 | * Interface LikeableService. 19 | * 20 | * @package Cog\Likeable\Contracts 21 | */ 22 | interface LikeableService 23 | { 24 | /** 25 | * Add a like to likeable model by user. 26 | * 27 | * @param \Cog\Likeable\Contracts\Likeable $likeable 28 | * @param string $type 29 | * @param string $userId 30 | * @return void 31 | * 32 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 33 | */ 34 | public function addLikeTo(LikeableContract $likeable, $type, $userId); 35 | 36 | /** 37 | * Remove a like to likeable model by user. 38 | * 39 | * @param \Cog\Likeable\Contracts\Likeable $likeable 40 | * @param string $type 41 | * @param int|null $userId 42 | * @return void 43 | * 44 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 45 | */ 46 | public function removeLikeFrom(LikeableContract $likeable, $type, $userId); 47 | 48 | /** 49 | * Toggle like for model by the given user. 50 | * 51 | * @param \Cog\Likeable\Contracts\Likeable $likeable 52 | * @param string $type 53 | * @param string $userId 54 | * @return void 55 | * 56 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 57 | */ 58 | public function toggleLikeOf(LikeableContract $likeable, $type, $userId); 59 | 60 | /** 61 | * Has the user already liked likeable model. 62 | * 63 | * @param \Cog\Likeable\Contracts\Likeable $likeable 64 | * @param string $type 65 | * @param int|null $userId 66 | * @return bool 67 | */ 68 | public function isLiked(LikeableContract $likeable, $type, $userId); 69 | 70 | /** 71 | * Decrement the total like count stored in the counter. 72 | * 73 | * @param \Cog\Likeable\Contracts\Likeable $likeable 74 | * @return void 75 | */ 76 | public function decrementLikesCount(LikeableContract $likeable); 77 | 78 | /** 79 | * Increment the total like count stored in the counter. 80 | * 81 | * @param \Cog\Likeable\Contracts\Likeable $likeable 82 | * @return void 83 | */ 84 | public function incrementLikesCount(LikeableContract $likeable); 85 | 86 | /** 87 | * Decrement the total dislike count stored in the counter. 88 | * 89 | * @param \Cog\Likeable\Contracts\Likeable $likeable 90 | * @return void 91 | */ 92 | public function decrementDislikesCount(LikeableContract $likeable); 93 | 94 | /** 95 | * Increment the total dislike count stored in the counter. 96 | * 97 | * @param \Cog\Likeable\Contracts\Likeable $likeable 98 | * @return void 99 | */ 100 | public function incrementDislikesCount(LikeableContract $likeable); 101 | 102 | /** 103 | * Remove like counters by likeable type. 104 | * 105 | * @param string $likeableType 106 | * @param string|null $type 107 | * @return void 108 | */ 109 | public function removeLikeCountersOfType($likeableType, $type = null); 110 | 111 | /** 112 | * Remove all likes from likeable model. 113 | * 114 | * @param \Cog\Likeable\Contracts\Likeable $likeable 115 | * @param string $type 116 | * @return void 117 | */ 118 | public function removeModelLikes(LikeableContract $likeable, $type); 119 | 120 | /** 121 | * Get collection of users who liked entity. 122 | * 123 | * @param \Cog\Likeable\Contracts\Likeable $likeable 124 | * @return \Illuminate\Support\Collection 125 | */ 126 | public function collectLikersOf(LikeableContract $likeable); 127 | 128 | /** 129 | * Get collection of users who disliked entity. 130 | * 131 | * @param \Cog\Likeable\Contracts\Likeable $likeable 132 | * @return \Illuminate\Support\Collection 133 | */ 134 | public function collectDislikersOf(LikeableContract $likeable); 135 | 136 | /** 137 | * Fetch records that are liked by a given user id. 138 | * 139 | * @param \Illuminate\Database\Eloquent\Builder $query 140 | * @param string $type 141 | * @param int|null $userId 142 | * @return \Illuminate\Database\Eloquent\Builder 143 | * 144 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 145 | */ 146 | public function scopeWhereLikedBy(Builder $query, $type, $userId); 147 | 148 | /** 149 | * Fetch records sorted by likes count. 150 | * 151 | * @param \Illuminate\Database\Eloquent\Builder $query 152 | * @param string $likeType 153 | * @param string $direction 154 | * @return \Illuminate\Database\Eloquent\Builder 155 | */ 156 | public function scopeOrderByLikesCount(Builder $query, $likeType, $direction = 'desc'); 157 | 158 | /** 159 | * Fetch likes counters data. 160 | * 161 | * @param string $likeableType 162 | * @param string $likeType 163 | * @return array 164 | */ 165 | public function fetchLikesCounters($likeableType, $likeType); 166 | } 167 | -------------------------------------------------------------------------------- /src/Enums/LikeType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Enums; 13 | 14 | /** 15 | * Class LikeType. 16 | * 17 | * @package Cog\Likeable\Enums 18 | */ 19 | class LikeType 20 | { 21 | const LIKE = 'like'; 22 | const DISLIKE = 'dislike'; 23 | } 24 | -------------------------------------------------------------------------------- /src/Events/ModelWasDisliked.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Events; 13 | 14 | use Cog\Likeable\Contracts\Likeable as LikeableContract; 15 | 16 | /** 17 | * Class ModelWasDisliked. 18 | * 19 | * @package Cog\Likeable\Events 20 | */ 21 | class ModelWasDisliked 22 | { 23 | /** 24 | * The disliked model. 25 | * 26 | * @var \Cog\Likeable\Contracts\Likeable 27 | */ 28 | public $model; 29 | 30 | /** 31 | * User id who liked model. 32 | * 33 | * @var int 34 | */ 35 | public $likerId; 36 | 37 | /** 38 | * Create a new event instance. 39 | * 40 | * @param \Cog\Likeable\Contracts\Likeable $likeable 41 | * @param int $likerId 42 | * @return void 43 | */ 44 | public function __construct(LikeableContract $likeable, $likerId) 45 | { 46 | $this->model = $likeable; 47 | $this->likerId = $likerId; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Events/ModelWasLiked.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Events; 13 | 14 | use Cog\Likeable\Contracts\Likeable as LikeableContract; 15 | 16 | /** 17 | * Class ModelWasLiked. 18 | * 19 | * @package Cog\Likeable\Events 20 | */ 21 | class ModelWasLiked 22 | { 23 | /** 24 | * The liked model. 25 | * 26 | * @var \Cog\Likeable\Contracts\Likeable 27 | */ 28 | public $model; 29 | 30 | /** 31 | * User id who liked model. 32 | * 33 | * @var int 34 | */ 35 | public $likerId; 36 | 37 | /** 38 | * Create a new event instance. 39 | * 40 | * @param \Cog\Likeable\Contracts\Likeable $likeable 41 | * @param int $likerId 42 | * @return void 43 | */ 44 | public function __construct(LikeableContract $likeable, $likerId) 45 | { 46 | $this->model = $likeable; 47 | $this->likerId = $likerId; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Events/ModelWasUndisliked.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Events; 13 | 14 | use Cog\Likeable\Contracts\Likeable as LikeableContract; 15 | 16 | /** 17 | * Class ModelWasUndisliked. 18 | * 19 | * @package Cog\Likeable\Events 20 | */ 21 | class ModelWasUndisliked 22 | { 23 | /** 24 | * The undisliked model. 25 | * 26 | * @var \Cog\Likeable\Contracts\Likeable 27 | */ 28 | public $model; 29 | 30 | /** 31 | * User id who unliked model. 32 | * 33 | * @var int 34 | */ 35 | public $likerId; 36 | 37 | /** 38 | * Create a new event instance. 39 | * 40 | * @param \Cog\Likeable\Contracts\Likeable $likeable 41 | * @param int $likerId 42 | * @return void 43 | */ 44 | public function __construct(LikeableContract $likeable, $likerId) 45 | { 46 | $this->model = $likeable; 47 | $this->likerId = $likerId; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Events/ModelWasUnliked.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Events; 13 | 14 | use Cog\Likeable\Contracts\Likeable as LikeableContract; 15 | 16 | /** 17 | * Class ModelWasUnliked. 18 | * 19 | * @package Cog\Likeable\Events 20 | */ 21 | class ModelWasUnliked 22 | { 23 | /** 24 | * The unliked model. 25 | * 26 | * @var \Cog\Likeable\Contracts\Likeable 27 | */ 28 | public $model; 29 | 30 | /** 31 | * User id who unliked model. 32 | * 33 | * @var int 34 | */ 35 | public $likerId; 36 | 37 | /** 38 | * Create a new event instance. 39 | * 40 | * @param \Cog\Likeable\Contracts\Likeable $likeable 41 | * @param int $likerId 42 | * @return void 43 | */ 44 | public function __construct(LikeableContract $likeable, $likerId) 45 | { 46 | $this->model = $likeable; 47 | $this->likerId = $likerId; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Exceptions/LikeTypeInvalidException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Exceptions; 13 | 14 | use Exception; 15 | 16 | /** 17 | * Class LikeTypeInvalidException. 18 | * 19 | * @package Cog\Likable\Exceptions 20 | */ 21 | class LikeTypeInvalidException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/LikerNotDefinedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Exceptions; 13 | 14 | use Exception; 15 | 16 | /** 17 | * Class LikerNotDefinedException. 18 | * 19 | * @package Cog\Likable\Exceptions 20 | */ 21 | class LikerNotDefinedException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/ModelInvalidException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Exceptions; 13 | 14 | use Exception; 15 | 16 | /** 17 | * Class ModelInvalidException. 18 | * 19 | * @package Cog\Likable\Exceptions 20 | */ 21 | class ModelInvalidException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Models/Like.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Models; 13 | 14 | use Cog\Likeable\Contracts\Like as LikeContract; 15 | use Illuminate\Database\Eloquent\Model; 16 | 17 | /** 18 | * Class Like. 19 | * 20 | * @property \Cog\Likeable\Contracts\Likeable likeable 21 | * @property int type_id 22 | * @property int user_id 23 | * @package Cog\Likeable\Models 24 | */ 25 | class Like extends Model implements LikeContract 26 | { 27 | /** 28 | * The table associated with the model. 29 | * 30 | * @var string 31 | */ 32 | protected $table = 'like'; 33 | 34 | /** 35 | * The attributes that are mass assignable. 36 | * 37 | * @var array 38 | */ 39 | protected $fillable = [ 40 | 'user_id', 41 | 'type_id', 42 | ]; 43 | 44 | /** 45 | * Set the value of the "updated at" attribute. 46 | * 47 | * @todo drop it in 4.0 by adding updated_at column 48 | * @deprecated 3.0 49 | * @param mixed $value 50 | * @return $this 51 | */ 52 | public function setUpdatedAt($value) 53 | { 54 | return $this; 55 | } 56 | 57 | /** 58 | * Likeable model relation. 59 | * 60 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 61 | */ 62 | public function likeable() 63 | { 64 | return $this->morphTo(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Models/LikeCounter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Models; 13 | 14 | use Cog\Likeable\Contracts\LikeCounter as LikeCounterContract; 15 | use Illuminate\Database\Eloquent\Model; 16 | 17 | /** 18 | * Class LikeCounter. 19 | * 20 | * @property int type_id 21 | * @property int count 22 | * @package Cog\Likeable\Models 23 | */ 24 | class LikeCounter extends Model implements LikeCounterContract 25 | { 26 | /** 27 | * Indicates if the model should be timestamped. 28 | * 29 | * @var bool 30 | */ 31 | public $timestamps = false; 32 | 33 | /** 34 | * The table associated with the model. 35 | * 36 | * @var string 37 | */ 38 | protected $table = 'like_counter'; 39 | 40 | /** 41 | * The attributes that are mass assignable. 42 | * 43 | * @var array 44 | */ 45 | protected $fillable = [ 46 | 'type_id', 47 | 'count', 48 | ]; 49 | 50 | /** 51 | * The attributes that should be cast to native types. 52 | * 53 | * @var array 54 | */ 55 | protected $casts = [ 56 | 'count' => 'integer', 57 | ]; 58 | 59 | /** 60 | * Likeable model relation. 61 | * 62 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 63 | */ 64 | public function likeable() 65 | { 66 | return $this->morphTo(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Observers/LikeObserver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Observers; 13 | 14 | use Cog\Likeable\Enums\LikeType; 15 | use Cog\Likeable\Events\ModelWasDisliked; 16 | use Cog\Likeable\Events\ModelWasLiked; 17 | use Cog\Likeable\Events\ModelWasUndisliked; 18 | use Cog\Likeable\Events\ModelWasUnliked; 19 | use Cog\Likeable\Contracts\Like as LikeContract; 20 | use Cog\Likeable\Contracts\LikeableService as LikeableServiceContract; 21 | 22 | /** 23 | * Class LikeObserver. 24 | * 25 | * @package Cog\Likeable\Observers 26 | */ 27 | class LikeObserver 28 | { 29 | /** 30 | * Handle the created event for the model. 31 | * 32 | * @param \Cog\Likeable\Contracts\Like $like 33 | * @return void 34 | */ 35 | public function created(LikeContract $like) 36 | { 37 | if ($like->type_id == LikeType::LIKE) { 38 | event(new ModelWasLiked($like->likeable, $like->user_id)); 39 | app(LikeableServiceContract::class)->incrementLikesCount($like->likeable); 40 | } else { 41 | event(new ModelWasDisliked($like->likeable, $like->user_id)); 42 | app(LikeableServiceContract::class)->incrementDislikesCount($like->likeable); 43 | } 44 | } 45 | 46 | /** 47 | * Handle the deleted event for the model. 48 | * 49 | * @param \Cog\Likeable\Contracts\Like $like 50 | * @return void 51 | */ 52 | public function deleted(LikeContract $like) 53 | { 54 | if ($like->type_id == LikeType::LIKE) { 55 | event(new ModelWasUnliked($like->likeable, $like->user_id)); 56 | app(LikeableServiceContract::class)->decrementLikesCount($like->likeable); 57 | } else { 58 | event(new ModelWasUndisliked($like->likeable, $like->user_id)); 59 | app(LikeableServiceContract::class)->decrementDislikesCount($like->likeable); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Observers/ModelObserver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Observers; 13 | 14 | use Cog\Likeable\Contracts\Likeable as LikeableContract; 15 | 16 | /** 17 | * Class ModelObserver. 18 | * 19 | * @package Cog\Likeable\Observers 20 | */ 21 | class ModelObserver 22 | { 23 | /** 24 | * Handle the deleted event for the model. 25 | * 26 | * @param \Cog\Likeable\Contracts\Likeable $likeable 27 | * @return void 28 | */ 29 | public function deleted(LikeableContract $likeable) 30 | { 31 | if (!$this->removeLikesOnDelete($likeable)) { 32 | return; 33 | } 34 | 35 | $likeable->removeLikes(); 36 | } 37 | 38 | /** 39 | * Should remove likes on model delete (defaults to true). 40 | * 41 | * @param \Cog\Likeable\Contracts\Likeable $likeable 42 | * @return bool 43 | */ 44 | protected function removeLikesOnDelete(LikeableContract $likeable) 45 | { 46 | return isset($likeable->removeLikesOnDelete) ? $likeable->removeLikesOnDelete : true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Providers/LikeableServiceProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Providers; 13 | 14 | use Cog\Likeable\Console\LikeableRecountCommand; 15 | use Cog\Likeable\Contracts\Like as LikeContract; 16 | use Cog\Likeable\Contracts\LikeableService as LikeableServiceContract; 17 | use Cog\Likeable\Contracts\LikeCounter as LikeCounterContract; 18 | use Cog\Likeable\Models\Like; 19 | use Cog\Likeable\Models\LikeCounter; 20 | use Cog\Likeable\Observers\LikeObserver; 21 | use Cog\Likeable\Services\LikeableService; 22 | use Illuminate\Support\ServiceProvider; 23 | 24 | /** 25 | * Class LikeableServiceProvider. 26 | * 27 | * @package Cog\Likeable\Providers 28 | */ 29 | class LikeableServiceProvider extends ServiceProvider 30 | { 31 | /** 32 | * Bootstrap the application events. 33 | * 34 | * @return void 35 | */ 36 | public function boot() 37 | { 38 | $this->registerConsoleCommands(); 39 | $this->registerObservers(); 40 | $this->registerPublishes(); 41 | } 42 | 43 | /** 44 | * Register the service provider. 45 | * 46 | * @return void 47 | */ 48 | public function register() 49 | { 50 | $this->registerContracts(); 51 | } 52 | 53 | /** 54 | * Register Likeable's models observers. 55 | * 56 | * @return void 57 | */ 58 | protected function registerObservers() 59 | { 60 | $this->app->make(LikeContract::class)->observe(LikeObserver::class); 61 | } 62 | 63 | /** 64 | * Register Likeable's console commands. 65 | * 66 | * @return void 67 | */ 68 | protected function registerConsoleCommands() 69 | { 70 | if ($this->app->runningInConsole()) { 71 | $this->commands([ 72 | LikeableRecountCommand::class, 73 | ]); 74 | } 75 | } 76 | 77 | /** 78 | * Register Likeable's classes in the container. 79 | * 80 | * @return void 81 | */ 82 | protected function registerContracts() 83 | { 84 | $this->app->bind(LikeContract::class, Like::class); 85 | $this->app->bind(LikeCounterContract::class, LikeCounter::class); 86 | $this->app->singleton(LikeableServiceContract::class, LikeableService::class); 87 | } 88 | 89 | /** 90 | * Setup the resource publishing groups for Likeable. 91 | * 92 | * @return void 93 | */ 94 | protected function registerPublishes() 95 | { 96 | if ($this->app->runningInConsole()) { 97 | $this->publishes([ 98 | __DIR__ . '/../../database/migrations' => database_path('migrations'), 99 | ], 'migrations'); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Services/LikeableService.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Services; 13 | 14 | use Cog\Likeable\Contracts\Likeable as LikeableContract; 15 | use Cog\Likeable\Contracts\Like as LikeContract; 16 | use Cog\Likeable\Contracts\LikeableService as LikeableServiceContract; 17 | use Cog\Likeable\Contracts\LikeCounter as LikeCounterContract; 18 | use Cog\Likeable\Enums\LikeType; 19 | use Cog\Likeable\Exceptions\LikerNotDefinedException; 20 | use Cog\Likeable\Exceptions\LikeTypeInvalidException; 21 | use Illuminate\Database\Query\JoinClause; 22 | use Illuminate\Support\Facades\DB; 23 | use Illuminate\Database\Eloquent\Builder; 24 | 25 | /** 26 | * Class LikeableService. 27 | * 28 | * @package Cog\Likeable\Services 29 | */ 30 | class LikeableService implements LikeableServiceContract 31 | { 32 | /** 33 | * Add a like to likeable model by user. 34 | * 35 | * @param \Cog\Likeable\Contracts\Likeable $likeable 36 | * @param string $type 37 | * @param string $userId 38 | * @return void 39 | * 40 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 41 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 42 | */ 43 | public function addLikeTo(LikeableContract $likeable, $type, $userId) 44 | { 45 | $userId = $this->getLikerUserId($userId); 46 | 47 | $like = $likeable->likesAndDislikes()->where([ 48 | 'user_id' => $userId, 49 | ])->first(); 50 | 51 | if (!$like) { 52 | $likeable->likes()->create([ 53 | 'user_id' => $userId, 54 | 'type_id' => $this->getLikeTypeId($type), 55 | ]); 56 | 57 | return; 58 | } 59 | 60 | if ($like->type_id == $this->getLikeTypeId($type)) { 61 | return; 62 | } 63 | 64 | $like->delete(); 65 | 66 | $likeable->likes()->create([ 67 | 'user_id' => $userId, 68 | 'type_id' => $this->getLikeTypeId($type), 69 | ]); 70 | } 71 | 72 | /** 73 | * Remove a like to likeable model by user. 74 | * 75 | * @param \Cog\Likeable\Contracts\Likeable $likeable 76 | * @param string $type 77 | * @param int|null $userId 78 | * @return void 79 | * 80 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 81 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 82 | */ 83 | public function removeLikeFrom(LikeableContract $likeable, $type, $userId) 84 | { 85 | $like = $likeable->likesAndDislikes()->where([ 86 | 'user_id' => $this->getLikerUserId($userId), 87 | 'type_id' => $this->getLikeTypeId($type), 88 | ])->first(); 89 | 90 | if (!$like) { 91 | return; 92 | } 93 | 94 | $like->delete(); 95 | } 96 | 97 | /** 98 | * Toggle like for model by the given user. 99 | * 100 | * @param \Cog\Likeable\Contracts\Likeable $likeable 101 | * @param string $type 102 | * @param string $userId 103 | * @return void 104 | * 105 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 106 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 107 | */ 108 | public function toggleLikeOf(LikeableContract $likeable, $type, $userId) 109 | { 110 | $userId = $this->getLikerUserId($userId); 111 | 112 | $like = $likeable->likesAndDislikes()->where([ 113 | 'user_id' => $userId, 114 | 'type_id' => $this->getLikeTypeId($type), 115 | ])->exists(); 116 | 117 | if ($like) { 118 | $this->removeLikeFrom($likeable, $type, $userId); 119 | } else { 120 | $this->addLikeTo($likeable, $type, $userId); 121 | } 122 | } 123 | 124 | /** 125 | * Has the user already liked likeable model. 126 | * 127 | * @param \Cog\Likeable\Contracts\Likeable $likeable 128 | * @param string $type 129 | * @param int|null $userId 130 | * @return bool 131 | * 132 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 133 | */ 134 | public function isLiked(LikeableContract $likeable, $type, $userId) 135 | { 136 | if (is_null($userId)) { 137 | $userId = $this->loggedInUserId(); 138 | } 139 | 140 | if (!$userId) { 141 | return false; 142 | } 143 | 144 | $typeId = $this->getLikeTypeId($type); 145 | 146 | $exists = $this->hasLikeOrDislikeInLoadedRelation($likeable, $typeId, $userId); 147 | if (!is_null($exists)) { 148 | return $exists; 149 | } 150 | 151 | return $likeable->likesAndDislikes()->where([ 152 | 'user_id' => $userId, 153 | 'type_id' => $typeId, 154 | ])->exists(); 155 | } 156 | 157 | /** 158 | * Decrement the total like count stored in the counter. 159 | * 160 | * @param \Cog\Likeable\Contracts\Likeable $likeable 161 | * @return void 162 | */ 163 | public function decrementLikesCount(LikeableContract $likeable) 164 | { 165 | $counter = $likeable->likesCounter()->first(); 166 | 167 | if (!$counter) { 168 | return; 169 | } 170 | 171 | $counter->decrement('count'); 172 | } 173 | 174 | /** 175 | * Increment the total like count stored in the counter. 176 | * 177 | * @param \Cog\Likeable\Contracts\Likeable $likeable 178 | * @return void 179 | */ 180 | public function incrementLikesCount(LikeableContract $likeable) 181 | { 182 | $counter = $likeable->likesCounter()->first(); 183 | 184 | if (!$counter) { 185 | $counter = $likeable->likesCounter()->create([ 186 | 'count' => 0, 187 | 'type_id' => LikeType::LIKE, 188 | ]); 189 | } 190 | 191 | $counter->increment('count'); 192 | } 193 | 194 | /** 195 | * Decrement the total dislike count stored in the counter. 196 | * 197 | * @param \Cog\Likeable\Contracts\Likeable $likeable 198 | * @return void 199 | */ 200 | public function decrementDislikesCount(LikeableContract $likeable) 201 | { 202 | $counter = $likeable->dislikesCounter()->first(); 203 | 204 | if (!$counter) { 205 | return; 206 | } 207 | 208 | $counter->decrement('count'); 209 | } 210 | 211 | /** 212 | * Increment the total dislike count stored in the counter. 213 | * 214 | * @param \Cog\Likeable\Contracts\Likeable $likeable 215 | * @return void 216 | */ 217 | public function incrementDislikesCount(LikeableContract $likeable) 218 | { 219 | $counter = $likeable->dislikesCounter()->first(); 220 | 221 | if (!$counter) { 222 | $counter = $likeable->dislikesCounter()->create([ 223 | 'count' => 0, 224 | 'type_id' => LikeType::DISLIKE, 225 | ]); 226 | } 227 | 228 | $counter->increment('count'); 229 | } 230 | 231 | /** 232 | * Remove like counters by likeable type. 233 | * 234 | * @param string $likeableType 235 | * @param string|null $type 236 | * @return void 237 | * 238 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 239 | */ 240 | public function removeLikeCountersOfType($likeableType, $type = null) 241 | { 242 | if (class_exists($likeableType)) { 243 | /** @var \Cog\Likeable\Contracts\Likeable $likeable */ 244 | $likeable = new $likeableType; 245 | $likeableType = $likeable->getMorphClass(); 246 | } 247 | 248 | /** @var \Illuminate\Database\Eloquent\Builder $counters */ 249 | $counters = app(LikeCounterContract::class)->where('likeable_type', $likeableType); 250 | if (!is_null($type)) { 251 | $counters->where('type_id', $this->getLikeTypeId($type)); 252 | } 253 | $counters->delete(); 254 | } 255 | 256 | /** 257 | * Remove all likes from likeable model. 258 | * 259 | * @param \Cog\Likeable\Contracts\Likeable $likeable 260 | * @param string $type 261 | * @return void 262 | * 263 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 264 | */ 265 | public function removeModelLikes(LikeableContract $likeable, $type) 266 | { 267 | app(LikeContract::class)->where([ 268 | 'likeable_id' => $likeable->getKey(), 269 | 'likeable_type' => $likeable->getMorphClass(), 270 | 'type_id' => $this->getLikeTypeId($type), 271 | ])->delete(); 272 | 273 | app(LikeCounterContract::class)->where([ 274 | 'likeable_id' => $likeable->getKey(), 275 | 'likeable_type' => $likeable->getMorphClass(), 276 | 'type_id' => $this->getLikeTypeId($type), 277 | ])->delete(); 278 | } 279 | 280 | /** 281 | * Get collection of users who liked entity. 282 | * 283 | * @param \Cog\Likeable\Contracts\Likeable $likeable 284 | * @return \Illuminate\Support\Collection 285 | */ 286 | public function collectLikersOf(LikeableContract $likeable) 287 | { 288 | $userModel = $this->resolveUserModel(); 289 | 290 | $likersIds = $likeable->likes->pluck('user_id'); 291 | 292 | return $userModel::whereKey($likersIds)->get(); 293 | } 294 | 295 | /** 296 | * Get collection of users who disliked entity. 297 | * 298 | * @param \Cog\Likeable\Contracts\Likeable $likeable 299 | * @return \Illuminate\Support\Collection 300 | */ 301 | public function collectDislikersOf(LikeableContract $likeable) 302 | { 303 | $userModel = $this->resolveUserModel(); 304 | 305 | $likersIds = $likeable->dislikes->pluck('user_id'); 306 | 307 | return $userModel::whereKey($likersIds)->get(); 308 | } 309 | 310 | /** 311 | * Fetch records that are liked by a given user id. 312 | * 313 | * @param \Illuminate\Database\Eloquent\Builder $query 314 | * @param string $type 315 | * @param int|null $userId 316 | * @return \Illuminate\Database\Eloquent\Builder 317 | * 318 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 319 | */ 320 | public function scopeWhereLikedBy(Builder $query, $type, $userId) 321 | { 322 | $userId = $this->getLikerUserId($userId); 323 | 324 | return $query->whereHas('likesAndDislikes', function (Builder $innerQuery) use ($type, $userId) { 325 | $innerQuery->where('user_id', $userId); 326 | $innerQuery->where('type_id', $this->getLikeTypeId($type)); 327 | }); 328 | } 329 | 330 | /** 331 | * Fetch records sorted by likes count. 332 | * 333 | * @param \Illuminate\Database\Eloquent\Builder $query 334 | * @param string $likeType 335 | * @param string $direction 336 | * @return \Illuminate\Database\Eloquent\Builder 337 | */ 338 | public function scopeOrderByLikesCount(Builder $query, $likeType, $direction = 'desc') 339 | { 340 | $likeable = $query->getModel(); 341 | 342 | return $query 343 | ->select($likeable->getTable() . '.*', 'like_counter.count') 344 | ->leftJoin('like_counter', function (JoinClause $join) use ($likeable, $likeType) { 345 | $join 346 | ->on('like_counter.likeable_id', '=', "{$likeable->getTable()}.{$likeable->getKeyName()}") 347 | ->where('like_counter.likeable_type', '=', $likeable->getMorphClass()) 348 | ->where('like_counter.type_id', '=', $this->getLikeTypeId($likeType)); 349 | }) 350 | ->orderBy('like_counter.count', $direction); 351 | } 352 | 353 | /** 354 | * Fetch likes counters data. 355 | * 356 | * @param string $likeableType 357 | * @param string $likeType 358 | * @return array 359 | * 360 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 361 | */ 362 | public function fetchLikesCounters($likeableType, $likeType) 363 | { 364 | /** @var \Illuminate\Database\Eloquent\Builder $likesCount */ 365 | $likesCount = app(LikeContract::class) 366 | ->select([ 367 | DB::raw('COUNT(*) AS count'), 368 | 'likeable_type', 369 | 'likeable_id', 370 | 'type_id', 371 | ]) 372 | ->where('likeable_type', $likeableType); 373 | 374 | if (!is_null($likeType)) { 375 | $likesCount->where('type_id', $this->getLikeTypeId($likeType)); 376 | } 377 | 378 | $likesCount->groupBy('likeable_id', 'type_id'); 379 | 380 | return $likesCount->get()->toArray(); 381 | } 382 | 383 | /** 384 | * Get current user id or get user id passed in. 385 | * 386 | * @param int $userId 387 | * @return int 388 | * 389 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 390 | */ 391 | protected function getLikerUserId($userId) 392 | { 393 | if (is_null($userId)) { 394 | $userId = $this->loggedInUserId(); 395 | } 396 | 397 | if (!$userId) { 398 | throw new LikerNotDefinedException(); 399 | } 400 | 401 | return $userId; 402 | } 403 | 404 | /** 405 | * Fetch the primary ID of the currently logged in user. 406 | * 407 | * @return int 408 | */ 409 | protected function loggedInUserId() 410 | { 411 | return auth()->id(); 412 | } 413 | 414 | /** 415 | * Get like type id from name. 416 | * 417 | * @todo move to Enum class 418 | * @param string $type 419 | * @return int 420 | * 421 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 422 | */ 423 | protected function getLikeTypeId($type) 424 | { 425 | $type = strtoupper($type); 426 | if (!defined("\\Cog\\Likeable\\Enums\\LikeType::{$type}")) { 427 | throw new LikeTypeInvalidException("Like type `{$type}` not exist"); 428 | } 429 | 430 | return constant("\\Cog\\Likeable\\Enums\\LikeType::{$type}"); 431 | } 432 | 433 | /** 434 | * Retrieve User's model class name. 435 | * 436 | * @return \Illuminate\Contracts\Auth\Authenticatable 437 | */ 438 | private function resolveUserModel() 439 | { 440 | return config('auth.providers.users.model'); 441 | } 442 | 443 | /** 444 | * @param \Cog\Likeable\Contracts\Likeable $likeable 445 | * @param string $typeId 446 | * @param int $userId 447 | * @return bool|null 448 | * 449 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 450 | */ 451 | private function hasLikeOrDislikeInLoadedRelation(LikeableContract $likeable, $typeId, $userId) 452 | { 453 | $relations = $this->likeTypeRelations($typeId); 454 | 455 | foreach ($relations as $relation) { 456 | if (!$likeable->relationLoaded($relation)) { 457 | continue; 458 | } 459 | 460 | return $likeable->{$relation}->contains(function ($item) use ($userId, $typeId) { 461 | return $item->user_id == $userId && $item->type_id === $typeId; 462 | }); 463 | } 464 | 465 | return null; 466 | } 467 | 468 | /** 469 | * Resolve list of likeable relations by like type. 470 | * 471 | * @param string $type 472 | * @return array 473 | * 474 | * @throws \Cog\Likeable\Exceptions\LikeTypeInvalidException 475 | */ 476 | private function likeTypeRelations($type) 477 | { 478 | $relations = [ 479 | LikeType::LIKE => [ 480 | 'likes', 481 | 'likesAndDislikes', 482 | ], 483 | LikeType::DISLIKE => [ 484 | 'dislikes', 485 | 'likesAndDislikes', 486 | ], 487 | ]; 488 | 489 | if (!isset($relations[$type])) { 490 | throw new LikeTypeInvalidException("Like type `{$type}` not supported"); 491 | } 492 | 493 | return $relations[$type]; 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /src/Traits/HasLikes.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Traits; 13 | 14 | /** 15 | * Trait HasLikes. 16 | * 17 | * @deprecated 3.0 18 | * @see \Cog\Likeable\Traits\Likeable 19 | * @package Cog\Likeable\Traits 20 | */ 21 | trait HasLikes 22 | { 23 | use Likeable; 24 | } 25 | -------------------------------------------------------------------------------- /src/Traits/Likeable.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Cog\Likeable\Traits; 13 | 14 | use Cog\Likeable\Contracts\Like as LikeContract; 15 | use Cog\Likeable\Contracts\LikeableService as LikeableServiceContract; 16 | use Cog\Likeable\Contracts\LikeCounter as LikeCounterContract; 17 | use Cog\Likeable\Enums\LikeType; 18 | use Cog\Likeable\Observers\ModelObserver; 19 | use Illuminate\Database\Eloquent\Builder; 20 | 21 | /** 22 | * Trait Likeable. 23 | * 24 | * @package Cog\Likeable\Traits 25 | */ 26 | trait Likeable 27 | { 28 | /** 29 | * Boot the Likeable trait for a model. 30 | * 31 | * @return void 32 | */ 33 | public static function bootLikeable() 34 | { 35 | static::observe(ModelObserver::class); 36 | } 37 | 38 | /** 39 | * Collection of likes and dislikes on this record. 40 | * 41 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 42 | */ 43 | public function likesAndDislikes() 44 | { 45 | return $this->morphMany(app(LikeContract::class), 'likeable'); 46 | } 47 | 48 | /** 49 | * Collection of likes on this record. 50 | * 51 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 52 | */ 53 | public function likes() 54 | { 55 | return $this->likesAndDislikes()->where('type_id', LikeType::LIKE); 56 | } 57 | 58 | /** 59 | * Collection of dislikes on this record. 60 | * 61 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 62 | */ 63 | public function dislikes() 64 | { 65 | return $this->likesAndDislikes()->where('type_id', LikeType::DISLIKE); 66 | } 67 | 68 | /** 69 | * Counter is a record that stores the total likes for the morphed record. 70 | * 71 | * @return \Illuminate\Database\Eloquent\Relations\MorphOne 72 | */ 73 | public function likesCounter() 74 | { 75 | return $this->morphOne(app(LikeCounterContract::class), 'likeable') 76 | ->where('type_id', LikeType::LIKE); 77 | } 78 | 79 | /** 80 | * Counter is a record that stores the total dislikes for the morphed record. 81 | * 82 | * @return \Illuminate\Database\Eloquent\Relations\MorphOne 83 | */ 84 | public function dislikesCounter() 85 | { 86 | return $this->morphOne(app(LikeCounterContract::class), 'likeable') 87 | ->where('type_id', LikeType::DISLIKE); 88 | } 89 | 90 | /** 91 | * Fetch users who liked entity. 92 | * 93 | * @return \Illuminate\Support\Collection 94 | */ 95 | public function collectLikers() 96 | { 97 | return app(LikeableServiceContract::class)->collectLikersOf($this); 98 | } 99 | 100 | /** 101 | * Fetch users who disliked entity. 102 | * 103 | * @return \Illuminate\Support\Collection 104 | */ 105 | public function collectDislikers() 106 | { 107 | return app(LikeableServiceContract::class)->collectDislikersOf($this); 108 | } 109 | 110 | /** 111 | * Model likesCount attribute. 112 | * 113 | * @return int 114 | */ 115 | public function getLikesCountAttribute() 116 | { 117 | return $this->likesCounter ? $this->likesCounter->count : 0; 118 | } 119 | 120 | /** 121 | * Model dislikesCount attribute. 122 | * 123 | * @return int 124 | */ 125 | public function getDislikesCountAttribute() 126 | { 127 | return $this->dislikesCounter ? $this->dislikesCounter->count : 0; 128 | } 129 | 130 | /** 131 | * Did the currently logged in user like this model. 132 | * 133 | * @return bool 134 | */ 135 | public function getLikedAttribute() 136 | { 137 | return $this->liked(); 138 | } 139 | 140 | /** 141 | * Did the currently logged in user dislike this model. 142 | * 143 | * @return bool 144 | */ 145 | public function getDislikedAttribute() 146 | { 147 | return $this->disliked(); 148 | } 149 | 150 | /** 151 | * Difference between likes and dislikes count. 152 | * 153 | * @return int 154 | */ 155 | public function getLikesDiffDislikesCountAttribute() 156 | { 157 | return $this->likesCount - $this->dislikesCount; 158 | } 159 | 160 | /** 161 | * Fetch records that are liked by a given user id. 162 | * 163 | * @param \Illuminate\Database\Eloquent\Builder $query 164 | * @param int|null $userId 165 | * @return \Illuminate\Database\Eloquent\Builder 166 | * 167 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 168 | */ 169 | public function scopeWhereLikedBy(Builder $query, $userId = null) 170 | { 171 | return app(LikeableServiceContract::class) 172 | ->scopeWhereLikedBy($query, LikeType::LIKE, $userId); 173 | } 174 | 175 | /** 176 | * Fetch records that are disliked by a given user id. 177 | * 178 | * @param \Illuminate\Database\Eloquent\Builder $query 179 | * @param int|null $userId 180 | * @return \Illuminate\Database\Eloquent\Builder 181 | * 182 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 183 | */ 184 | public function scopeWhereDislikedBy(Builder $query, $userId = null) 185 | { 186 | return app(LikeableServiceContract::class) 187 | ->scopeWhereLikedBy($query, LikeType::DISLIKE, $userId); 188 | } 189 | 190 | /** 191 | * Fetch records sorted by likes count. 192 | * 193 | * @param \Illuminate\Database\Eloquent\Builder $query 194 | * @param string $direction 195 | * @return \Illuminate\Database\Eloquent\Builder 196 | */ 197 | public function scopeOrderByLikesCount(Builder $query, $direction = 'desc') 198 | { 199 | return app(LikeableServiceContract::class) 200 | ->scopeOrderByLikesCount($query, LikeType::LIKE, $direction); 201 | } 202 | 203 | /** 204 | * Fetch records sorted by likes count. 205 | * 206 | * @param \Illuminate\Database\Eloquent\Builder $query 207 | * @param string $direction 208 | * @return \Illuminate\Database\Eloquent\Builder 209 | */ 210 | public function scopeOrderByDislikesCount(Builder $query, $direction = 'desc') 211 | { 212 | return app(LikeableServiceContract::class) 213 | ->scopeOrderByLikesCount($query, LikeType::DISLIKE, $direction); 214 | } 215 | 216 | /** 217 | * Add a like for model by the given user. 218 | * 219 | * @param mixed $userId If null will use currently logged in user. 220 | * @return void 221 | * 222 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 223 | */ 224 | public function like($userId = null) 225 | { 226 | app(LikeableServiceContract::class)->addLikeTo($this, LikeType::LIKE, $userId); 227 | } 228 | 229 | /** 230 | * Remove a like from this record for the given user. 231 | * 232 | * @param int|null $userId If null will use currently logged in user. 233 | * @return void 234 | * 235 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 236 | */ 237 | public function unlike($userId = null) 238 | { 239 | app(LikeableServiceContract::class)->removeLikeFrom($this, LikeType::LIKE, $userId); 240 | } 241 | 242 | /** 243 | * Toggle like for model by the given user. 244 | * 245 | * @param mixed $userId If null will use currently logged in user. 246 | * @return void 247 | * 248 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 249 | */ 250 | public function likeToggle($userId = null) 251 | { 252 | app(LikeableServiceContract::class)->toggleLikeOf($this, LikeType::LIKE, $userId); 253 | } 254 | 255 | /** 256 | * Has the user already liked likeable model. 257 | * 258 | * @param int|null $userId 259 | * @return bool 260 | */ 261 | public function liked($userId = null) 262 | { 263 | return app(LikeableServiceContract::class)->isLiked($this, LikeType::LIKE, $userId); 264 | } 265 | 266 | /** 267 | * Delete likes related to the current record. 268 | * 269 | * @return void 270 | */ 271 | public function removeLikes() 272 | { 273 | app(LikeableServiceContract::class)->removeModelLikes($this, LikeType::LIKE); 274 | } 275 | 276 | /** 277 | * Add a dislike for model by the given user. 278 | * 279 | * @param mixed $userId If null will use currently logged in user. 280 | * @return void 281 | * 282 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 283 | */ 284 | public function dislike($userId = null) 285 | { 286 | app(LikeableServiceContract::class)->addLikeTo($this, LikeType::DISLIKE, $userId); 287 | } 288 | 289 | /** 290 | * Remove a dislike from this record for the given user. 291 | * 292 | * @param int|null $userId If null will use currently logged in user. 293 | * @return void 294 | * 295 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 296 | */ 297 | public function undislike($userId = null) 298 | { 299 | app(LikeableServiceContract::class)->removeLikeFrom($this, LikeType::DISLIKE, $userId); 300 | } 301 | 302 | /** 303 | * Toggle dislike for model by the given user. 304 | * 305 | * @param mixed $userId If null will use currently logged in user. 306 | * @return void 307 | * 308 | * @throws \Cog\Likeable\Exceptions\LikerNotDefinedException 309 | */ 310 | public function dislikeToggle($userId = null) 311 | { 312 | app(LikeableServiceContract::class)->toggleLikeOf($this, LikeType::DISLIKE, $userId); 313 | } 314 | 315 | /** 316 | * Has the user already disliked likeable model. 317 | * 318 | * @param int|null $userId 319 | * @return bool 320 | */ 321 | public function disliked($userId = null) 322 | { 323 | return app(LikeableServiceContract::class)->isLiked($this, LikeType::DISLIKE, $userId); 324 | } 325 | 326 | /** 327 | * Delete dislikes related to the current record. 328 | * 329 | * @return void 330 | */ 331 | public function removeDislikes() 332 | { 333 | app(LikeableServiceContract::class)->removeModelLikes($this, LikeType::DISLIKE); 334 | } 335 | } 336 | --------------------------------------------------------------------------------