├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── FriendshipsServiceProvider.php ├── Models │ ├── FriendFriendshipGroups.php │ └── Friendship.php ├── Status.php ├── Traits │ └── Friendable.php ├── config │ └── friendships.php └── database │ └── migrations │ ├── create_friendships_groups_table.php │ └── create_friendships_table.php └── tests ├── CreatesApplication.php ├── FriendshipsEventsTest.php ├── FriendshipsGroupsTest.php ├── FriendshipsTest.php ├── Stub_User.php ├── TestCase.php ├── config ├── app.php ├── database.php └── friendships.php └── helpers.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | /.idea 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | services: 4 | - mysql 5 | 6 | php: 7 | - 5.6 8 | - 7.0 9 | 10 | addons: 11 | code_climate: 12 | repo_token: bb9681de04daca830a87c3eb0f6500b0a815f8262107fec833e0852df2401466 13 | 14 | sudo: false 15 | 16 | # cache vendor dirs 17 | cache: 18 | directories: 19 | - vendor 20 | - $HOME/.composer/cache 21 | 22 | install: 23 | - COMPOSER_DISCARD_CHANGES=1 composer install --dev --prefer-source --no-interaction 24 | 25 | before_script: 26 | - mysql -u root -e 'set global innodb_large_prefix=1;' 27 | - mysql -u root -e 'set global innodb_file_format="Barracuda";' 28 | - mysql -u root -e 'set global innodb_file_format_max="Barracuda";' 29 | - mysql -u root -e 'set global innodb_file_per_table=true;' 30 | - mysql -e 'create database friendships_test;' 31 | - cp src/database/migrations/create_friendships_table.php vendor/laravel/laravel/database/migrations/2015_11_19_053825_create_friendships_table.php 32 | - yes | cp src/database/migrations/create_friendships_groups_table.php vendor/laravel/laravel/database/migrations/2015_11_19_053826_create_friendships_groups_table.php 33 | - yes | cp tests/config/friendships.php vendor/laravel/laravel/config/friendships.php 34 | - yes | cp tests/Stub_User.php vendor/laravel/laravel/app/User.php 35 | - cd vendor/laravel/laravel 36 | - composer update --dev --prefer-source --no-interaction 37 | - perl -pi -w -e "s/'engine' => null,/'engine' => 'InnoDB ROW_FORMAT=DYNAMIC',/g;" config/database.php 38 | - php artisan migrate 39 | - cd - 40 | 41 | script: 42 | - vendor/bin/phpunit 43 | 44 | after_script: 45 | - vendor/bin/test-reporter 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, **thank you** for contributing! 4 | 5 | Here are a few rules to follow in order to ease code reviews and merging: 6 | 7 | - follow [PSR-1](http://www.php-fig.org/psr/1/) and [PSR-2](http://www.php-fig.org/psr/2/) 8 | - run the test suite 9 | - write (or update) unit tests when applicable 10 | - write documentation for new features 11 | - use [commit messages that make sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 12 | 13 | One may ask you to [squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) too. This is used to "clean" your pull request before merging it (we don't want commits such as `fix tests`, `fix 2`, `fix 3`, etc.). 14 | 15 | When creating your pull request on GitHub, please write a description which gives the context and/or explains why you are creating it. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Alex Kyriakidis 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel 5 Friendships 2 | 3 | [![Build Status](https://travis-ci.org/hootlex/laravel-friendships.svg?branch=v1.0.21)](https://travis-ci.org/hootlex/laravel-friendships) [![Code Climate](https://codeclimate.com/github/hootlex/laravel-friendships/badges/gpa.svg)](https://codeclimate.com/github/hootlex/laravel-friendships) [![Test Coverage](https://codeclimate.com/github/hootlex/laravel-friendships/badges/coverage.svg)](https://codeclimate.com/github/hootlex/laravel-friendships/coverage) [![Total Downloads](https://img.shields.io/packagist/dt/hootlex/laravel-friendships.svg?style=flat)](https://packagist.org/packages/hootlex/laravel-friendships) [![Version](https://img.shields.io/packagist/v/hootlex/laravel-friendships.svg?style=flat)](https://packagist.org/packages/hootlex/laravel-friendships) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE) [![Join the chat at https://gitter.im/laravel-friendships/Lobby](https://badges.gitter.im/laravel-friendships/Lobby.svg)](https://gitter.im/laravel-friendships/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | 6 | This package gives Eloquent models the ability to manage their friendships. 7 | You can easily design a Facebook like Friend System. 8 | 9 | ## Models can: 10 | - Send Friend Requests 11 | - Accept Friend Requests 12 | - Deny Friend Requests 13 | - Block Another Model 14 | - Group Friends 15 | 16 | ## Installation 17 | 18 | First, install the package through Composer. 19 | 20 | ```php 21 | composer require hootlex/laravel-friendships 22 | ``` 23 | 24 | If you are using Laravel < 5.5, you need to add Hootlex\Friendships\FriendshipsServiceProvider to your `config/app.php` providers array: 25 | ```php 26 | Hootlex\Friendships\FriendshipsServiceProvider::class, 27 | ``` 28 | Publish config and migrations 29 | ``` 30 | php artisan vendor:publish --provider="Hootlex\Friendships\FriendshipsServiceProvider" 31 | ``` 32 | Configure the published config in 33 | ``` 34 | config\friendships.php 35 | ``` 36 | Finally, migrate the database 37 | ``` 38 | php artisan migrate 39 | ``` 40 | 41 | ## Setup a Model 42 | ```php 43 | use Hootlex\Friendships\Traits\Friendable; 44 | class User extends Model 45 | { 46 | use Friendable; 47 | ... 48 | } 49 | ``` 50 | 51 | ## How to use 52 | [Check the Test file to see the package in action](https://github.com/hootlex/laravel-friendships/blob/master/tests/FriendshipsTest.php) 53 | 54 | #### Send a Friend Request 55 | ```php 56 | $user->befriend($recipient); 57 | ``` 58 | 59 | #### Accept a Friend Request 60 | ```php 61 | $user->acceptFriendRequest($sender); 62 | ``` 63 | 64 | #### Deny a Friend Request 65 | ```php 66 | $user->denyFriendRequest($sender); 67 | ``` 68 | 69 | #### Remove Friend 70 | ```php 71 | $user->unfriend($friend); 72 | ``` 73 | 74 | #### Block a Model 75 | ```php 76 | $user->blockFriend($friend); 77 | ``` 78 | 79 | #### Unblock a Model 80 | ```php 81 | $user->unblockFriend($friend); 82 | ``` 83 | 84 | #### Check if Model is Friend with another Model 85 | ```php 86 | $user->isFriendWith($friend); 87 | ``` 88 | 89 | 90 | #### Check if Model has a pending friend request from another Model 91 | ```php 92 | $user->hasFriendRequestFrom($sender); 93 | ``` 94 | 95 | #### Check if Model has already sent a friend request to another Model 96 | ```php 97 | $user->hasSentFriendRequestTo($recipient); 98 | ``` 99 | 100 | #### Check if Model has blocked another Model 101 | ```php 102 | $user->hasBlocked($friend); 103 | ``` 104 | 105 | #### Check if Model is blocked by another Model 106 | ```php 107 | $user->isBlockedBy($friend); 108 | ``` 109 | 110 | #### Get a single friendship 111 | ```php 112 | $user->getFriendship($friend); 113 | ``` 114 | 115 | #### Get a list of all Friendships 116 | ```php 117 | $user->getAllFriendships(); 118 | ``` 119 | 120 | #### Get a list of pending Friendships 121 | ```php 122 | $user->getPendingFriendships(); 123 | ``` 124 | 125 | #### Get a list of accepted Friendships 126 | ```php 127 | $user->getAcceptedFriendships(); 128 | ``` 129 | 130 | #### Get a list of denied Friendships 131 | ```php 132 | $user->getDeniedFriendships(); 133 | ``` 134 | 135 | #### Get a list of blocked Friendships 136 | ```php 137 | $user->getBlockedFriendships(); 138 | ``` 139 | 140 | #### Get a list of pending Friend Requests 141 | ```php 142 | $user->getFriendRequests(); 143 | ``` 144 | 145 | #### Get the number of Friends 146 | ```php 147 | $user->getFriendsCount(); 148 | ``` 149 | #### Get the number of Pendings 150 | ```php 151 | $user->getPendingsCount(); 152 | ``` 153 | 154 | #### Get the number of mutual Friends with another user 155 | ```php 156 | $user->getMutualFriendsCount($otherUser); 157 | ``` 158 | 159 | ## Friends 160 | To get a collection of friend models (ex. User) use the following methods: 161 | #### Get Friends 162 | ```php 163 | $user->getFriends(); 164 | ``` 165 | 166 | #### Get Friends Paginated 167 | ```php 168 | $user->getFriends($perPage = 20); 169 | ``` 170 | 171 | #### Get Friends of Friends 172 | ```php 173 | $user->getFriendsOfFriends($perPage = 20); 174 | ``` 175 | 176 | #### Collection of Friends in specific group paginated: 177 | ```php 178 | $user->getFriends($perPage = 20, $group_name); 179 | ``` 180 | 181 | #### Get mutual Friends with another user 182 | ```php 183 | $user->getMutualFriends($otherUser, $perPage = 20); 184 | ``` 185 | 186 | ## Friend groups 187 | The friend groups are defined in the `config/friendships.php` file. 188 | The package comes with a few default groups. 189 | To modify them, or add your own, you need to specify a `slug` and a `key`. 190 | 191 | ```php 192 | // config/friendships.php 193 | ... 194 | 'groups' => [ 195 | 'acquaintances' => 0, 196 | 'close_friends' => 1, 197 | 'family' => 2 198 | ] 199 | ``` 200 | 201 | Since you've configured friend groups, you can group/ungroup friends using the following methods. 202 | 203 | #### Group a Friend 204 | ```php 205 | $user->groupFriend($friend, $group_name); 206 | ``` 207 | 208 | #### Remove a Friend from family group 209 | ```php 210 | $user->ungroupFriend($friend, 'family'); 211 | ``` 212 | 213 | #### Remove a Friend from all groups 214 | ```php 215 | $user->ungroupFriend($friend); 216 | ``` 217 | 218 | #### Get the number of Friends in specific group 219 | ```php 220 | $user->getFriendsCount($group_name); 221 | ``` 222 | 223 | ### To filter `friendships` by group you can pass a group slug. 224 | ```php 225 | $user->getAllFriendships($group_name); 226 | $user->getAcceptedFriendships($group_name); 227 | $user->getPendingFriendships($group_name); 228 | ... 229 | ``` 230 | 231 | ## Events 232 | This is the list of the events fired by default for each action 233 | 234 | |Event name |Fired | 235 | |----------------------|---------------------------------| 236 | |friendships.sent |When a friend request is sent | 237 | |friendships.accepted |When a friend request is accepted| 238 | |friendships.denied |When a friend request is denied | 239 | |friendships.blocked |When a friend is blocked | 240 | |friendships.unblocked |When a friend is unblocked | 241 | |friendships.cancelled |When a friendship is cancelled | 242 | 243 | ## Contributing 244 | See the [CONTRIBUTING](CONTRIBUTING.md) guide. 245 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hootlex/laravel-friendships", 3 | "description": "This package gives Eloquent models the ability to manage their friendships.", 4 | "keywords": ["laravel", "friendships", "friend-system", "friends", "eloquent"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Hootlex", 9 | "email": "hootlex@icloud.com" 10 | } 11 | ], 12 | "autoload-dev": { 13 | "classmap": [ 14 | "vendor/laravel/laravel/tests/TestCase.php" 15 | ], 16 | "files": [ 17 | "tests/helpers.php" 18 | ], 19 | "psr-4": { 20 | "Tests\\": "tests/" 21 | } 22 | }, 23 | "require": { 24 | "php": ">=5.4.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit" : "5.*", 28 | "fzaninotto/faker": "~1.4", 29 | "laravel/laravel": "5.*", 30 | "codeclimate/php-test-reporter": "^0.3.2", 31 | "mockery/mockery": "^0.9.5", 32 | "doctrine/dbal": "^2.5" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Hootlex\\Friendships\\": "src/" 37 | } 38 | }, 39 | "extra": { 40 | "laravel": { 41 | "providers": [ 42 | "Hootlex\\Friendships\\FriendshipsServiceProvider" 43 | ] 44 | } 45 | }, 46 | "minimum-stability": "stable" 47 | } 48 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | ./tests 13 | 14 | 15 | 16 | 17 | ./src 18 | 19 | src/config 20 | src/database/migrations 21 | src/Status.php 22 | src/FriendshipsServiceProvider.php 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/FriendshipsServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 25 | $stub . 'create_friendships_table.php' => $target . date('Y_m_d_His', time()) . '_create_friendships_table.php', 26 | $stub . 'create_friendships_groups_table.php' => $target . date('Y_m_d_His', time() + 1) . '_create_friendships_groups_table.php' 27 | ], 'migrations'); 28 | 29 | $this->publishes([ 30 | __DIR__ . '/config/friendships.php' => config_path('friendships.php'), 31 | ], 'config'); 32 | 33 | } 34 | 35 | /** 36 | * Register any application services. 37 | * 38 | * @return void 39 | */ 40 | public function register() 41 | { 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Models/FriendFriendshipGroups.php: -------------------------------------------------------------------------------- 1 | table = config('friendships.tables.fr_groups_pivot'); 31 | 32 | parent::__construct($attributes); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Models/Friendship.php: -------------------------------------------------------------------------------- 1 | table = config('friendships.tables.fr_pivot'); 26 | 27 | parent::__construct($attributes); 28 | } 29 | 30 | /** 31 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 32 | */ 33 | public function sender() 34 | { 35 | return $this->morphTo('sender'); 36 | } 37 | 38 | /** 39 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 40 | */ 41 | public function recipient() 42 | { 43 | return $this->morphTo('recipient'); 44 | } 45 | 46 | /** 47 | * @return \Illuminate\Database\Eloquent\Relations\hasMany 48 | */ 49 | public function groups() { 50 | return $this->hasMany(FriendFriendshipGroups::class, 'friendship_id'); 51 | } 52 | 53 | /** 54 | * @param Model $recipient 55 | * @return $this 56 | */ 57 | public function fillRecipient($recipient) 58 | { 59 | return $this->fill([ 60 | 'recipient_id' => $recipient->getKey(), 61 | 'recipient_type' => $recipient->getMorphClass() 62 | ]); 63 | } 64 | 65 | /** 66 | * @param $query 67 | * @param Model $model 68 | * @return \Illuminate\Database\Eloquent\Builder 69 | */ 70 | public function scopeWhereRecipient($query, $model) 71 | { 72 | return $query->where('recipient_id', $model->getKey()) 73 | ->where('recipient_type', $model->getMorphClass()); 74 | } 75 | 76 | /** 77 | * @param $query 78 | * @param Model $model 79 | * @return \Illuminate\Database\Eloquent\Builder 80 | */ 81 | public function scopeWhereSender($query, $model) 82 | { 83 | return $query->where('sender_id', $model->getKey()) 84 | ->where('sender_type', $model->getMorphClass()); 85 | } 86 | 87 | /** 88 | * @param $query 89 | * @param Model $model 90 | * @param string $groupSlug 91 | * @return \Illuminate\Database\Eloquent\Builder 92 | */ 93 | public function scopeWhereGroup($query, $model, $groupSlug) 94 | { 95 | 96 | $groupsPivotTable = config('friendships.tables.fr_groups_pivot'); 97 | $friendsPivotTable = config('friendships.tables.fr_pivot'); 98 | $groupsAvailable = config('friendships.groups', []); 99 | 100 | if ('' !== $groupSlug && isset($groupsAvailable[$groupSlug])) { 101 | 102 | $groupId = $groupsAvailable[$groupSlug]; 103 | 104 | $query->join($groupsPivotTable, function ($join) use ($groupsPivotTable, $friendsPivotTable, $groupId, $model) { 105 | $join->on($groupsPivotTable . '.friendship_id', '=', $friendsPivotTable . '.id') 106 | ->where($groupsPivotTable . '.group_id', '=', $groupId) 107 | ->where(function ($query) use ($groupsPivotTable, $friendsPivotTable, $model) { 108 | $query->where($groupsPivotTable . '.friend_id', '!=', $model->getKey()) 109 | ->where($groupsPivotTable . '.friend_type', '=', $model->getMorphClass()); 110 | }) 111 | ->orWhere($groupsPivotTable . '.friend_type', '!=', $model->getMorphClass()); 112 | }); 113 | 114 | } 115 | 116 | return $query; 117 | 118 | } 119 | 120 | /** 121 | * @param $query 122 | * @param Model $sender 123 | * @param Model $recipient 124 | * @return \Illuminate\Database\Eloquent\Builder 125 | */ 126 | public function scopeBetweenModels($query, $sender, $recipient) 127 | { 128 | $query->where(function ($queryIn) use ($sender, $recipient){ 129 | $queryIn->where(function ($q) use ($sender, $recipient) { 130 | $q->whereSender($sender)->whereRecipient($recipient); 131 | })->orWhere(function ($q) use ($sender, $recipient) { 132 | $q->whereSender($recipient)->whereRecipient($sender); 133 | }); 134 | }); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/Status.php: -------------------------------------------------------------------------------- 1 | canBefriend($recipient)) { 26 | return false; 27 | } 28 | 29 | $friendship = (new Friendship)->fillRecipient($recipient)->fill([ 30 | 'status' => Status::PENDING, 31 | ]); 32 | 33 | $this->friends()->save($friendship); 34 | 35 | Event::fire('friendships.sent', [$this, $recipient]); 36 | 37 | return $friendship; 38 | 39 | } 40 | 41 | /** 42 | * @param Model $recipient 43 | * 44 | * @return bool 45 | */ 46 | public function unfriend(Model $recipient) 47 | { 48 | $deleted = $this->findFriendship($recipient)->delete(); 49 | 50 | Event::fire('friendships.cancelled', [$this, $recipient]); 51 | 52 | return $deleted; 53 | } 54 | 55 | /** 56 | * @param Model $recipient 57 | * 58 | * @return bool 59 | */ 60 | public function hasFriendRequestFrom(Model $recipient) 61 | { 62 | return $this->findFriendship($recipient)->whereSender($recipient)->whereStatus(Status::PENDING)->exists(); 63 | } 64 | 65 | /** 66 | * @param Model $recipient 67 | * 68 | * @return bool 69 | */ 70 | public function hasSentFriendRequestTo(Model $recipient) 71 | { 72 | return Friendship::whereRecipient($recipient)->whereSender($this)->whereStatus(Status::PENDING)->exists(); 73 | } 74 | 75 | /** 76 | * @param Model $recipient 77 | * 78 | * @return bool 79 | */ 80 | public function isFriendWith(Model $recipient) 81 | { 82 | return $this->findFriendship($recipient)->where('status', Status::ACCEPTED)->exists(); 83 | } 84 | 85 | /** 86 | * @param Model $recipient 87 | * 88 | * @return bool|int 89 | */ 90 | public function acceptFriendRequest(Model $recipient) 91 | { 92 | $updated = $this->findFriendship($recipient)->whereRecipient($this)->update([ 93 | 'status' => Status::ACCEPTED, 94 | ]); 95 | 96 | Event::fire('friendships.accepted', [$this, $recipient]); 97 | 98 | return $updated; 99 | } 100 | 101 | /** 102 | * @param Model $recipient 103 | * 104 | * @return bool|int 105 | */ 106 | public function denyFriendRequest(Model $recipient) 107 | { 108 | $updated = $this->findFriendship($recipient)->whereRecipient($this)->update([ 109 | 'status' => Status::DENIED, 110 | ]); 111 | 112 | Event::fire('friendships.denied', [$this, $recipient]); 113 | 114 | return $updated; 115 | } 116 | 117 | 118 | /** 119 | * @param Model $friend 120 | * @param $groupSlug 121 | * @return bool 122 | */ 123 | public function groupFriend(Model $friend, $groupSlug) 124 | { 125 | 126 | $friendship = $this->findFriendship($friend)->whereStatus(Status::ACCEPTED)->first(); 127 | $groupsAvailable = config('friendships.groups', []); 128 | 129 | if (!isset($groupsAvailable[$groupSlug]) || empty($friendship)) { 130 | return false; 131 | } 132 | 133 | $group = $friendship->groups()->firstOrCreate([ 134 | 'friendship_id' => $friendship->id, 135 | 'group_id' => $groupsAvailable[$groupSlug], 136 | 'friend_id' => $friend->getKey(), 137 | 'friend_type' => $friend->getMorphClass(), 138 | ]); 139 | 140 | return $group->wasRecentlyCreated; 141 | 142 | } 143 | 144 | /** 145 | * @param Model $friend 146 | * @param $groupSlug 147 | * @return bool 148 | */ 149 | public function ungroupFriend(Model $friend, $groupSlug = '') 150 | { 151 | 152 | $friendship = $this->findFriendship($friend)->first(); 153 | $groupsAvailable = config('friendships.groups', []); 154 | 155 | if (empty($friendship)) { 156 | return false; 157 | } 158 | 159 | $where = [ 160 | 'friendship_id' => $friendship->id, 161 | 'friend_id' => $friend->getKey(), 162 | 'friend_type' => $friend->getMorphClass(), 163 | ]; 164 | 165 | if ('' !== $groupSlug && isset($groupsAvailable[$groupSlug])) { 166 | $where['group_id'] = $groupsAvailable[$groupSlug]; 167 | } 168 | 169 | $result = $friendship->groups()->where($where)->delete(); 170 | 171 | return $result; 172 | 173 | } 174 | 175 | /** 176 | * @param Model $recipient 177 | * 178 | * @return \Hootlex\Friendships\Models\Friendship 179 | */ 180 | public function blockFriend(Model $recipient) 181 | { 182 | // if there is a friendship between the two users and the sender is not blocked 183 | // by the recipient user then delete the friendship 184 | if (!$this->isBlockedBy($recipient)) { 185 | $this->findFriendship($recipient)->delete(); 186 | } 187 | 188 | $friendship = (new Friendship)->fillRecipient($recipient)->fill([ 189 | 'status' => Status::BLOCKED, 190 | ]); 191 | 192 | $this->friends()->save($friendship); 193 | 194 | Event::fire('friendships.blocked', [$this, $recipient]); 195 | 196 | return $friendship; 197 | } 198 | 199 | /** 200 | * @param Model $recipient 201 | * 202 | * @return mixed 203 | */ 204 | public function unblockFriend(Model $recipient) 205 | { 206 | $deleted = $this->findFriendship($recipient)->whereSender($this)->delete(); 207 | 208 | Event::fire('friendships.unblocked', [$this, $recipient]); 209 | 210 | return $deleted; 211 | } 212 | 213 | /** 214 | * @param Model $recipient 215 | * 216 | * @return \Hootlex\Friendships\Models\Friendship 217 | */ 218 | public function getFriendship(Model $recipient) 219 | { 220 | return $this->findFriendship($recipient)->first(); 221 | } 222 | 223 | /** 224 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 225 | * 226 | * @param string $groupSlug 227 | * 228 | */ 229 | public function getAllFriendships($groupSlug = '') 230 | { 231 | return $this->findFriendships(null, $groupSlug)->get(); 232 | } 233 | 234 | /** 235 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 236 | * 237 | * @param string $groupSlug 238 | * 239 | */ 240 | public function getPendingFriendships($groupSlug = '') 241 | { 242 | return $this->findFriendships(Status::PENDING, $groupSlug)->get(); 243 | } 244 | 245 | /** 246 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 247 | * 248 | * @param string $groupSlug 249 | * 250 | */ 251 | public function getAcceptedFriendships($groupSlug = '') 252 | { 253 | return $this->findFriendships(Status::ACCEPTED, $groupSlug)->get(); 254 | } 255 | 256 | /** 257 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 258 | * 259 | */ 260 | public function getDeniedFriendships() 261 | { 262 | return $this->findFriendships(Status::DENIED)->get(); 263 | } 264 | 265 | /** 266 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 267 | * 268 | */ 269 | public function getBlockedFriendships() 270 | { 271 | return $this->findFriendships(Status::BLOCKED)->get(); 272 | } 273 | 274 | /** 275 | * @param Model $recipient 276 | * 277 | * @return bool 278 | */ 279 | public function hasBlocked(Model $recipient) 280 | { 281 | return $this->friends()->whereRecipient($recipient)->whereStatus(Status::BLOCKED)->exists(); 282 | } 283 | 284 | /** 285 | * @param Model $recipient 286 | * 287 | * @return bool 288 | */ 289 | public function isBlockedBy(Model $recipient) 290 | { 291 | return $recipient->hasBlocked($this); 292 | } 293 | 294 | /** 295 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 296 | */ 297 | public function getFriendRequests() 298 | { 299 | return Friendship::whereRecipient($this)->whereStatus(Status::PENDING)->get(); 300 | } 301 | 302 | /** 303 | * This method will not return Friendship models 304 | * It will return the 'friends' models. ex: App\User 305 | * 306 | * @param int $perPage Number 307 | * @param string $groupSlug 308 | * 309 | * @return \Illuminate\Database\Eloquent\Collection 310 | */ 311 | public function getFriends($perPage = 0, $groupSlug = '') 312 | { 313 | return $this->getOrPaginate($this->getFriendsQueryBuilder($groupSlug), $perPage); 314 | } 315 | 316 | /** 317 | * This method will not return Friendship models 318 | * It will return the 'friends' models. ex: App\User 319 | * 320 | * @param int $perPage Number 321 | * 322 | * @return \Illuminate\Database\Eloquent\Collection 323 | */ 324 | public function getMutualFriends(Model $other, $perPage = 0) 325 | { 326 | return $this->getOrPaginate($this->getMutualFriendsQueryBuilder($other), $perPage); 327 | } 328 | 329 | /** 330 | * Get the number of friends 331 | * 332 | * @return integer 333 | */ 334 | public function getMutualFriendsCount($other) 335 | { 336 | return $this->getMutualFriendsQueryBuilder($other)->count(); 337 | } 338 | 339 | /** 340 | * This method will not return Friendship models 341 | * It will return the 'friends' models. ex: App\User 342 | * 343 | * @param int $perPage Number 344 | * 345 | * @return \Illuminate\Database\Eloquent\Collection 346 | */ 347 | public function getFriendsOfFriends($perPage = 0) 348 | { 349 | return $this->getOrPaginate($this->friendsOfFriendsQueryBuilder(), $perPage); 350 | } 351 | 352 | 353 | /** 354 | * Get the number of friends 355 | * 356 | * @param string $groupSlug 357 | * 358 | * @return integer 359 | */ 360 | public function getFriendsCount($groupSlug = '') 361 | { 362 | $friendsCount = $this->findFriendships(Status::ACCEPTED, $groupSlug)->count(); 363 | return $friendsCount; 364 | } 365 | 366 | /** 367 | * @param Model $recipient 368 | * 369 | * @return bool 370 | */ 371 | public function canBefriend($recipient) 372 | { 373 | // if user has Blocked the recipient and changed his mind 374 | // he can send a friend request after unblocking 375 | if ($this->hasBlocked($recipient)) { 376 | $this->unblockFriend($recipient); 377 | return true; 378 | } 379 | 380 | // if sender has a friendship with the recipient return false 381 | if ($friendship = $this->getFriendship($recipient)) { 382 | // if previous friendship was Denied then let the user send fr 383 | if ($friendship->status != Status::DENIED) { 384 | return false; 385 | } 386 | } 387 | return true; 388 | } 389 | 390 | /** 391 | * @param Model $recipient 392 | * 393 | * @return \Illuminate\Database\Eloquent\Builder 394 | */ 395 | private function findFriendship(Model $recipient) 396 | { 397 | return Friendship::betweenModels($this, $recipient); 398 | } 399 | 400 | /** 401 | * @param $status 402 | * @param string $groupSlug 403 | * 404 | * @return \Illuminate\Database\Eloquent\Collection 405 | */ 406 | private function findFriendships($status = null, $groupSlug = '') 407 | { 408 | 409 | $query = Friendship::where(function ($query) { 410 | $query->where(function ($q) { 411 | $q->whereSender($this); 412 | })->orWhere(function ($q) { 413 | $q->whereRecipient($this); 414 | }); 415 | })->whereGroup($this, $groupSlug); 416 | 417 | //if $status is passed, add where clause 418 | if (!is_null($status)) { 419 | $query->where('status', $status); 420 | } 421 | 422 | return $query; 423 | } 424 | 425 | /** 426 | * Get the query builder of the 'friend' model 427 | * 428 | * @param string $groupSlug 429 | * 430 | * @return \Illuminate\Database\Eloquent\Builder 431 | */ 432 | private function getFriendsQueryBuilder($groupSlug = '') 433 | { 434 | 435 | $friendships = $this->findFriendships(Status::ACCEPTED, $groupSlug)->get(['sender_id', 'recipient_id']); 436 | $recipients = $friendships->pluck('recipient_id')->all(); 437 | $senders = $friendships->pluck('sender_id')->all(); 438 | 439 | return $this->where('id', '!=', $this->getKey())->whereIn('id', array_merge($recipients, $senders)); 440 | } 441 | 442 | /** 443 | * Get the query builder of the 'friend' model 444 | * 445 | * @return \Illuminate\Database\Eloquent\Builder 446 | */ 447 | private function getMutualFriendsQueryBuilder(Model $other) 448 | { 449 | $user1['friendships'] = $this->findFriendships(Status::ACCEPTED)->get(['sender_id', 'recipient_id']); 450 | $user1['recipients'] = $user1['friendships']->pluck('recipient_id')->all(); 451 | $user1['senders'] = $user1['friendships']->pluck('sender_id')->all(); 452 | 453 | $user2['friendships'] = $other->findFriendships(Status::ACCEPTED)->get(['sender_id', 'recipient_id']); 454 | $user2['recipients'] = $user2['friendships']->pluck('recipient_id')->all(); 455 | $user2['senders'] = $user2['friendships']->pluck('sender_id')->all(); 456 | 457 | $mutualFriendships = array_unique( 458 | array_intersect( 459 | array_merge($user1['recipients'], $user1['senders']), 460 | array_merge($user2['recipients'], $user2['senders']) 461 | ) 462 | ); 463 | 464 | return $this->whereNotIn('id', [$this->getKey(), $other->getKey()])->whereIn('id', $mutualFriendships); 465 | } 466 | 467 | /** 468 | * Get the query builder for friendsOfFriends ('friend' model) 469 | * 470 | * @param string $groupSlug 471 | * 472 | * @return \Illuminate\Database\Eloquent\Builder 473 | */ 474 | private function friendsOfFriendsQueryBuilder($groupSlug = '') 475 | { 476 | $friendships = $this->findFriendships(Status::ACCEPTED)->get(['sender_id', 'recipient_id']); 477 | $recipients = $friendships->pluck('recipient_id')->all(); 478 | $senders = $friendships->pluck('sender_id')->all(); 479 | 480 | $friendIds = array_unique(array_merge($recipients, $senders)); 481 | 482 | 483 | $fofs = Friendship::where('status', Status::ACCEPTED) 484 | ->where(function ($query) use ($friendIds) { 485 | $query->where(function ($q) use ($friendIds) { 486 | $q->whereIn('sender_id', $friendIds); 487 | })->orWhere(function ($q) use ($friendIds) { 488 | $q->whereIn('recipient_id', $friendIds); 489 | }); 490 | }) 491 | ->whereGroup($this, $groupSlug) 492 | ->get(['sender_id', 'recipient_id']); 493 | 494 | $fofIds = array_unique( 495 | array_merge($fofs->pluck('sender_id')->all(), $fofs->pluck('recipient_id')->all()) 496 | ); 497 | 498 | // Alternative way using collection helpers 499 | // $fofIds = array_unique( 500 | // $fofs->map(function ($item) { 501 | // return [$item->sender_id, $item->recipient_id]; 502 | // })->flatten()->all() 503 | // ); 504 | 505 | 506 | return $this->whereIn('id', $fofIds)->whereNotIn('id', $friendIds); 507 | } 508 | 509 | /** 510 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 511 | */ 512 | public function friends() 513 | { 514 | return $this->morphMany(Friendship::class, 'sender'); 515 | } 516 | 517 | /** 518 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 519 | */ 520 | public function groups() 521 | { 522 | return $this->morphMany(FriendFriendshipGroups::class, 'friend'); 523 | } 524 | 525 | protected function getOrPaginate($builder, $perPage) 526 | { 527 | if ($perPage == 0) { 528 | return $builder->get(); 529 | } 530 | return $builder->paginate($perPage); 531 | } 532 | } 533 | -------------------------------------------------------------------------------- /src/config/friendships.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'fr_pivot' => 'friendships', 7 | 'fr_groups_pivot' => 'user_friendship_groups' 8 | ], 9 | 10 | 'groups' => [ 11 | 'acquaintances' => 0, 12 | 'close_friends' => 1, 13 | 'family' => 2 14 | ] 15 | 16 | ]; -------------------------------------------------------------------------------- /src/database/migrations/create_friendships_groups_table.php: -------------------------------------------------------------------------------- 1 | integer('friendship_id')->unsigned(); 17 | $table->morphs('friend'); 18 | $table->integer('group_id')->unsigned(); 19 | 20 | $table->foreign('friendship_id') 21 | ->references('id') 22 | ->on(config('friendships.tables.fr_pivot')) 23 | ->onDelete('cascade'); 24 | 25 | $table->unique(['friendship_id', 'friend_id', 'friend_type', 'group_id'], 'unique'); 26 | 27 | }); 28 | 29 | } 30 | 31 | public function down() { 32 | Schema::dropIfExists(config('friendships.tables.fr_groups_pivot')); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/database/migrations/create_friendships_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 13 | $table->morphs('sender'); 14 | $table->morphs('recipient'); 15 | $table->tinyInteger('status')->default(0); 16 | $table->timestamps(); 17 | }); 18 | 19 | } 20 | 21 | public function down() { 22 | Schema::dropIfExists(config('friendships.tables.fr_pivot')); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/FriendshipsEventsTest.php: -------------------------------------------------------------------------------- 1 | sender = createUser(); 20 | $this->recipient = createUser(); 21 | } 22 | 23 | public function tearDown() 24 | { 25 | Mockery::close(); 26 | } 27 | 28 | /** @test */ 29 | public function friend_request_is_sent() 30 | { 31 | Event::shouldReceive('fire')->once()->withArgs(['friendships.sent', Mockery::any()]); 32 | 33 | $this->sender->befriend($this->recipient); 34 | } 35 | 36 | /** @test */ 37 | public function friend_request_is_accepted() 38 | { 39 | $this->sender->befriend($this->recipient); 40 | Event::shouldReceive('fire')->once()->withArgs(['friendships.accepted', Mockery::any()]); 41 | 42 | $this->recipient->acceptFriendRequest($this->sender); 43 | } 44 | 45 | /** @test */ 46 | public function friend_request_is_denied() 47 | { 48 | $this->sender->befriend($this->recipient); 49 | Event::shouldReceive('fire')->once()->withArgs(['friendships.denied', Mockery::any()]); 50 | 51 | $this->recipient->denyFriendRequest($this->sender); 52 | } 53 | 54 | /** @test */ 55 | public function friend_is_blocked() 56 | { 57 | $this->sender->befriend($this->recipient); 58 | $this->recipient->acceptFriendRequest($this->sender); 59 | Event::shouldReceive('fire')->once()->withArgs(['friendships.blocked', Mockery::any()]); 60 | 61 | $this->recipient->blockFriend($this->sender); 62 | } 63 | 64 | /** @test */ 65 | public function friend_is_unblocked() 66 | { 67 | $this->sender->befriend($this->recipient); 68 | $this->recipient->acceptFriendRequest($this->sender); 69 | $this->recipient->blockFriend($this->sender); 70 | Event::shouldReceive('fire')->once()->withArgs(['friendships.unblocked', Mockery::any()]); 71 | 72 | $this->recipient->unblockFriend($this->sender); 73 | } 74 | 75 | /** @test */ 76 | public function friendship_is_cancelled() 77 | { 78 | $this->sender->befriend($this->recipient); 79 | $this->recipient->acceptFriendRequest($this->sender); 80 | Event::shouldReceive('fire')->once()->withArgs(['friendships.cancelled', Mockery::any()]); 81 | 82 | $this->recipient->unfriend($this->sender); 83 | } 84 | } -------------------------------------------------------------------------------- /tests/FriendshipsGroupsTest.php: -------------------------------------------------------------------------------- 1 | befriend($recipient); 25 | $recipient->acceptFriendRequest($sender); 26 | 27 | $this->assertTrue((boolean)$recipient->groupFriend($sender, 'acquaintances')); 28 | $this->assertTrue((boolean)$sender->groupFriend($recipient, 'family')); 29 | 30 | // it only adds a friend to a group once 31 | $this->assertFalse((boolean)$sender->groupFriend($recipient, 'family')); 32 | 33 | // expect that users have been attached to specified groups 34 | $this->assertCount(1, $sender->getFriends(0, 'family')); 35 | $this->assertCount(1, $recipient->getFriends(0, 'acquaintances')); 36 | 37 | $this->assertEquals($recipient->id, $sender->getFriends(0, 'family')->first()->id); 38 | $this->assertEquals($sender->id, $recipient->getFriends(0, 'acquaintances')->first()->id); 39 | 40 | } 41 | 42 | /** @test */ 43 | public function user_cannot_add_a_non_friend_to_a_group() 44 | { 45 | $sender = createUser(); 46 | $stranger = createUser(); 47 | 48 | $this->assertFalse((boolean)$sender->groupFriend($stranger, 'family')); 49 | $this->assertCount(0, $sender->getFriends(0, 'family')); 50 | } 51 | 52 | /** @test */ 53 | public function user_can_remove_a_friend_from_group() 54 | { 55 | $sender = createUser(); 56 | $recipient = createUser(); 57 | 58 | $sender->befriend($recipient); 59 | $recipient->acceptFriendRequest($sender); 60 | 61 | $recipient->groupFriend($sender, 'acquaintances'); 62 | $recipient->groupFriend($sender, 'family'); 63 | 64 | $this->assertEquals(1, $recipient->ungroupFriend($sender, 'acquaintances')); 65 | 66 | $this->assertCount(0, $sender->getFriends(0, 'acquaintances')); 67 | 68 | // expect that friend has been removed from acquaintances but not family 69 | $this->assertCount(0, $recipient->getFriends(0, 'acquaintances')); 70 | $this->assertCount(1, $recipient->getFriends(0, 'family')); 71 | } 72 | 73 | /** @test */ 74 | public function user_cannot_remove_a_non_existing_friend_from_group() 75 | { 76 | $sender = createUser(); 77 | $recipient = createUser(); 78 | $recipient2 = createUser(); 79 | 80 | $sender->befriend($recipient); 81 | 82 | $this->assertEquals(0, $recipient->ungroupFriend($sender, 'acquaintances')); 83 | $this->assertEquals(0, $recipient2->ungroupFriend($sender, 'acquaintances')); 84 | } 85 | 86 | /** @test */ 87 | public function user_can_remove_a_friend_from_all_groups() 88 | { 89 | $sender = createUser(); 90 | $recipient = createUser(); 91 | 92 | $sender->befriend($recipient); 93 | $recipient->acceptFriendRequest($sender); 94 | 95 | $sender->groupFriend($recipient, 'family'); 96 | $sender->groupFriend($recipient, 'acquaintances'); 97 | 98 | $sender->ungroupFriend($recipient); 99 | 100 | $this->assertCount(0, $sender->getFriends(0, 'family')); 101 | $this->assertCount(0, $sender->getFriends(0, 'acquaintances')); 102 | } 103 | 104 | /** @test */ 105 | public function it_returns_friends_of_a_group() 106 | { 107 | $sender = createUser(); 108 | $recipients = createUser([], 10); 109 | 110 | foreach ($recipients as $key => $recipient) { 111 | 112 | $sender->befriend($recipient); 113 | $recipient->acceptFriendRequest($sender); 114 | 115 | if ($key % 2 === 0) { 116 | $sender->groupFriend($recipient, 'family'); 117 | } 118 | 119 | } 120 | 121 | $this->assertCount(5, $sender->getFriends(0, 'family')); 122 | $this->assertCount(10, $sender->getFriends()); 123 | } 124 | 125 | 126 | /** @test */ 127 | public function it_returns_all_user_friendships_by_group() 128 | { 129 | $sender = createUser(); 130 | $recipients = createUser([], 5); 131 | 132 | foreach ($recipients as $key=>$recipient) { 133 | 134 | $sender->befriend($recipient); 135 | 136 | if ($key < 4) { 137 | 138 | $recipient->acceptFriendRequest($sender); 139 | if ($key < 3) { 140 | $sender->groupFriend($recipient, 'acquaintances'); 141 | } 142 | else { 143 | $sender->groupFriend($recipient, 'family'); 144 | } 145 | 146 | } 147 | else { 148 | $recipient->denyFriendRequest($sender); 149 | } 150 | 151 | } 152 | 153 | //Assertions 154 | 155 | $this->assertCount(3, $sender->getAllFriendships('acquaintances')); 156 | $this->assertCount(1, $sender->getAllFriendships('family')); 157 | $this->assertCount(0, $sender->getAllFriendships('close_friends')); 158 | $this->assertCount(5, $sender->getAllFriendships('whatever')); 159 | } 160 | 161 | 162 | /** @test */ 163 | public function it_returns_accepted_user_friendships_by_group() 164 | { 165 | $sender = createUser(); 166 | $recipients = createUser([], 4); 167 | 168 | foreach ($recipients as $recipient) { 169 | $sender->befriend($recipient); 170 | } 171 | 172 | $recipients[0]->acceptFriendRequest($sender); 173 | $recipients[1]->acceptFriendRequest($sender); 174 | $recipients[2]->denyFriendRequest($sender); 175 | 176 | $sender->groupFriend($recipients[0], 'family'); 177 | $sender->groupFriend($recipients[1], 'family'); 178 | 179 | $this->assertCount(2, $sender->getAcceptedFriendships('family')); 180 | 181 | } 182 | 183 | /** @test */ 184 | public function it_returns_accepted_user_friendships_number_by_group() 185 | { 186 | $sender = createUser(); 187 | $recipients = createUser([], 20)->chunk(5); 188 | 189 | foreach ($recipients->shift() as $recipient) { 190 | $sender->befriend($recipient); 191 | $recipient->acceptFriendRequest($sender); 192 | $sender->groupFriend($recipient, 'acquaintances'); 193 | } 194 | 195 | //Assertions 196 | 197 | $this->assertEquals(5, $sender->getFriendsCount('acquaintances')); 198 | $this->assertEquals(0, $sender->getFriendsCount('family')); 199 | $this->assertEquals(0, $recipient->getFriendsCount('acquaintances')); 200 | $this->assertEquals(0, $recipient->getFriendsCount('family')); 201 | } 202 | 203 | 204 | /** @test */ 205 | public function it_returns_user_friends_by_group_per_page() 206 | { 207 | $sender = createUser(); 208 | $recipients = createUser([], 6); 209 | 210 | foreach ($recipients as $recipient) { 211 | $sender->befriend($recipient); 212 | } 213 | 214 | $recipients[0]->acceptFriendRequest($sender); 215 | $recipients[1]->acceptFriendRequest($sender); 216 | 217 | $recipients[2]->denyFriendRequest($sender); 218 | 219 | $recipients[3]->acceptFriendRequest($sender); 220 | $recipients[4]->acceptFriendRequest($sender); 221 | 222 | $sender->groupFriend($recipients[0], 'acquaintances'); 223 | $sender->groupFriend($recipients[1], 'acquaintances'); 224 | $sender->groupFriend($recipients[3], 'acquaintances'); 225 | $sender->groupFriend($recipients[4], 'acquaintances'); 226 | 227 | $sender->groupFriend($recipients[0], 'close_friends'); 228 | $sender->groupFriend($recipients[3], 'close_friends'); 229 | 230 | $sender->groupFriend($recipients[4], 'family'); 231 | 232 | //Assertions 233 | 234 | $this->assertCount(2, $sender->getFriends(2, 'acquaintances')); 235 | $this->assertCount(4, $sender->getFriends(0, 'acquaintances')); 236 | $this->assertCount(4, $sender->getFriends(10, 'acquaintances')); 237 | 238 | $this->assertCount(2, $sender->getFriends(0, 'close_friends')); 239 | $this->assertCount(1, $sender->getFriends(1, 'close_friends')); 240 | 241 | $this->assertCount(1, $sender->getFriends(0, 'family')); 242 | 243 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getFriends(0, 'acquaintances')); 244 | } 245 | 246 | 247 | } -------------------------------------------------------------------------------- /tests/FriendshipsTest.php: -------------------------------------------------------------------------------- 1 | befriend($recipient); 20 | 21 | $this->assertCount(1, $recipient->getFriendRequests()); 22 | } 23 | 24 | /** @test */ 25 | public function user_can_not_send_a_friend_request_if_frienship_is_pending() 26 | { 27 | $sender = createUser(); 28 | $recipient = createUser(); 29 | $sender->befriend($recipient); 30 | $sender->befriend($recipient); 31 | $sender->befriend($recipient); 32 | 33 | $this->assertCount(1, $recipient->getFriendRequests()); 34 | } 35 | 36 | 37 | /** @test */ 38 | public function user_can_send_a_friend_request_if_frienship_is_denied() 39 | { 40 | $sender = createUser(); 41 | $recipient = createUser(); 42 | 43 | $sender->befriend($recipient); 44 | $recipient->denyFriendRequest($sender); 45 | 46 | $sender->befriend($recipient); 47 | 48 | $this->assertCount(1, $recipient->getFriendRequests()); 49 | } 50 | 51 | /** @test */ 52 | public function user_can_remove_a_friend_request() 53 | { 54 | $sender = createUser(); 55 | $recipient = createUser(); 56 | 57 | $sender->befriend($recipient); 58 | $this->assertCount(1, $recipient->getFriendRequests()); 59 | 60 | $sender->unfriend($recipient); 61 | $this->assertCount(0, $recipient->getFriendRequests()); 62 | 63 | // Can resend friend request after deleted 64 | $sender->befriend($recipient); 65 | $this->assertCount(1, $recipient->getFriendRequests()); 66 | 67 | $recipient->acceptFriendRequest($sender); 68 | $this->assertEquals(true, $recipient->isFriendWith($sender)); 69 | // Can remove friend request after accepted 70 | $sender->unfriend($recipient); 71 | $this->assertEquals(false, $recipient->isFriendWith($sender)); 72 | } 73 | 74 | /** @test */ 75 | public function user_is_friend_with_another_user_if_accepts_a_friend_request() 76 | { 77 | $sender = createUser(); 78 | $recipient = createUser(); 79 | //send fr 80 | $sender->befriend($recipient); 81 | //accept fr 82 | $recipient->acceptFriendRequest($sender); 83 | 84 | $this->assertTrue($recipient->isFriendWith($sender)); 85 | $this->assertTrue($sender->isFriendWith($recipient)); 86 | //fr has been delete 87 | $this->assertCount(0, $recipient->getFriendRequests()); 88 | } 89 | 90 | /** @test */ 91 | public function user_is_not_friend_with_another_user_until_he_accepts_a_friend_request() 92 | { 93 | $sender = createUser(); 94 | $recipient = createUser(); 95 | //send fr 96 | $sender->befriend($recipient); 97 | 98 | $this->assertFalse($recipient->isFriendWith($sender)); 99 | $this->assertFalse($sender->isFriendWith($recipient)); 100 | } 101 | 102 | /** @test */ 103 | public function user_has_friend_request_from_another_user_if_he_received_a_friend_request() 104 | { 105 | $sender = createUser(); 106 | $recipient = createUser(); 107 | //send fr 108 | $sender->befriend($recipient); 109 | 110 | $this->assertTrue($recipient->hasFriendRequestFrom($sender)); 111 | $this->assertFalse($sender->hasFriendRequestFrom($recipient)); 112 | } 113 | 114 | /** @test */ 115 | public function user_has_sent_friend_request_to_this_user_if_he_already_sent_request() 116 | { 117 | $sender = createUser(); 118 | $recipient = createUser(); 119 | //send fr 120 | $sender->befriend($recipient); 121 | 122 | $this->assertFalse($recipient->hasSentFriendRequestTo($sender)); 123 | $this->assertTrue($sender->hasSentFriendRequestTo($recipient)); 124 | } 125 | 126 | /** @test */ 127 | public function user_has_not_friend_request_from_another_user_if_he_accepted_the_friend_request() 128 | { 129 | $sender = createUser(); 130 | $recipient = createUser(); 131 | //send fr 132 | $sender->befriend($recipient); 133 | //accept fr 134 | $recipient->acceptFriendRequest($sender); 135 | 136 | $this->assertFalse($recipient->hasFriendRequestFrom($sender)); 137 | $this->assertFalse($sender->hasFriendRequestFrom($recipient)); 138 | } 139 | 140 | /** @test */ 141 | public function user_cannot_accept_his_own_friend_request() 142 | { 143 | $sender = createUser(); 144 | $recipient = createUser(); 145 | 146 | //send fr 147 | $sender->befriend($recipient); 148 | 149 | $sender->acceptFriendRequest($recipient); 150 | $this->assertFalse($recipient->isFriendWith($sender)); 151 | } 152 | 153 | /** @test */ 154 | public function user_can_deny_a_friend_request() 155 | { 156 | $sender = createUser(); 157 | $recipient = createUser(); 158 | $sender->befriend($recipient); 159 | 160 | $recipient->denyFriendRequest($sender); 161 | 162 | $this->assertFalse($recipient->isFriendWith($sender)); 163 | 164 | //fr has been delete 165 | $this->assertCount(0, $recipient->getFriendRequests()); 166 | $this->assertCount(1, $sender->getDeniedFriendships()); 167 | } 168 | 169 | /** @test */ 170 | public function user_can_block_another_user() 171 | { 172 | $sender = createUser(); 173 | $recipient = createUser(); 174 | 175 | $sender->blockFriend($recipient); 176 | 177 | $this->assertTrue($recipient->isBlockedBy($sender)); 178 | $this->assertTrue($sender->hasBlocked($recipient)); 179 | //sender is not blocked by receipient 180 | $this->assertFalse($sender->isBlockedBy($recipient)); 181 | $this->assertFalse($recipient->hasBlocked($sender)); 182 | } 183 | 184 | /** @test */ 185 | public function user_can_unblock_a_blocked_user() 186 | { 187 | $sender = createUser(); 188 | $recipient = createUser(); 189 | 190 | $sender->blockFriend($recipient); 191 | $sender->unblockFriend($recipient); 192 | 193 | $this->assertFalse($recipient->isBlockedBy($sender)); 194 | $this->assertFalse($sender->hasBlocked($recipient)); 195 | } 196 | 197 | /** @test */ 198 | public function user_block_is_permanent_unless_blocker_decides_to_unblock() 199 | { 200 | $sender = createUser(); 201 | $recipient = createUser(); 202 | 203 | $sender->blockFriend($recipient); 204 | $this->assertTrue($recipient->isBlockedBy($sender)); 205 | 206 | // now recipient blocks sender too 207 | $recipient->blockFriend($sender); 208 | 209 | // expect that both users have blocked each other 210 | $this->assertTrue($sender->isBlockedBy($recipient)); 211 | $this->assertTrue($recipient->isBlockedBy($sender)); 212 | 213 | $sender->unblockFriend($recipient); 214 | 215 | $this->assertTrue($sender->isBlockedBy($recipient)); 216 | $this->assertFalse($recipient->isBlockedBy($sender)); 217 | 218 | $recipient->unblockFriend($sender); 219 | $this->assertFalse($sender->isBlockedBy($recipient)); 220 | $this->assertFalse($recipient->isBlockedBy($sender)); 221 | } 222 | 223 | /** @test */ 224 | public function user_can_send_friend_request_to_user_who_is_blocked() 225 | { 226 | $sender = createUser(); 227 | $recipient = createUser(); 228 | 229 | $sender->blockFriend($recipient); 230 | $sender->befriend($recipient); 231 | $sender->befriend($recipient); 232 | 233 | $this->assertCount(1, $recipient->getFriendRequests()); 234 | } 235 | 236 | /** @test */ 237 | public function it_returns_all_user_friendships() 238 | { 239 | $sender = createUser(); 240 | $recipients = createUser([], 3); 241 | 242 | foreach ($recipients as $recipient) { 243 | $sender->befriend($recipient); 244 | } 245 | 246 | $recipients[0]->acceptFriendRequest($sender); 247 | $recipients[1]->acceptFriendRequest($sender); 248 | $recipients[2]->denyFriendRequest($sender); 249 | $this->assertCount(3, $sender->getAllFriendships()); 250 | } 251 | 252 | /** @test */ 253 | public function it_returns_accepted_user_friendships_number() 254 | { 255 | $sender = createUser(); 256 | $recipients = createUser([], 3); 257 | 258 | foreach ($recipients as $recipient) { 259 | $sender->befriend($recipient); 260 | } 261 | 262 | $recipients[0]->acceptFriendRequest($sender); 263 | $recipients[1]->acceptFriendRequest($sender); 264 | $recipients[2]->denyFriendRequest($sender); 265 | $this->assertEquals(2, $sender->getFriendsCount()); 266 | } 267 | 268 | /** @test */ 269 | public function it_returns_accepted_user_friendships() 270 | { 271 | $sender = createUser(); 272 | $recipients = createUser([], 3); 273 | 274 | foreach ($recipients as $recipient) { 275 | $sender->befriend($recipient); 276 | } 277 | 278 | $recipients[0]->acceptFriendRequest($sender); 279 | $recipients[1]->acceptFriendRequest($sender); 280 | $recipients[2]->denyFriendRequest($sender); 281 | $this->assertCount(2, $sender->getAcceptedFriendships()); 282 | } 283 | 284 | /** @test */ 285 | public function it_returns_only_accepted_user_friendships() 286 | { 287 | $sender = createUser(); 288 | $recipients = createUser([], 4); 289 | 290 | foreach ($recipients as $recipient) { 291 | $sender->befriend($recipient); 292 | } 293 | 294 | $recipients[0]->acceptFriendRequest($sender); 295 | $recipients[1]->acceptFriendRequest($sender); 296 | $recipients[2]->denyFriendRequest($sender); 297 | $this->assertCount(2, $sender->getAcceptedFriendships()); 298 | 299 | $this->assertCount(1, $recipients[0]->getAcceptedFriendships()); 300 | $this->assertCount(1, $recipients[1]->getAcceptedFriendships()); 301 | $this->assertCount(0, $recipients[2]->getAcceptedFriendships()); 302 | $this->assertCount(0, $recipients[3]->getAcceptedFriendships()); 303 | } 304 | 305 | /** @test */ 306 | public function it_returns_pending_user_friendships() 307 | { 308 | $sender = createUser(); 309 | $recipients = createUser([], 3); 310 | 311 | foreach ($recipients as $recipient) { 312 | $sender->befriend($recipient); 313 | } 314 | 315 | $recipients[0]->acceptFriendRequest($sender); 316 | $this->assertCount(2, $sender->getPendingFriendships()); 317 | } 318 | 319 | /** @test */ 320 | public function it_returns_denied_user_friendships() 321 | { 322 | $sender = createUser(); 323 | $recipients = createUser([], 3); 324 | 325 | foreach ($recipients as $recipient) { 326 | $sender->befriend($recipient); 327 | } 328 | 329 | $recipients[0]->acceptFriendRequest($sender); 330 | $recipients[1]->acceptFriendRequest($sender); 331 | $recipients[2]->denyFriendRequest($sender); 332 | $this->assertCount(1, $sender->getDeniedFriendships()); 333 | } 334 | 335 | /** @test */ 336 | public function it_returns_blocked_user_friendships() 337 | { 338 | $sender = createUser(); 339 | $recipients = createUser([], 3); 340 | 341 | foreach ($recipients as $recipient) { 342 | $sender->befriend($recipient); 343 | } 344 | 345 | $recipients[0]->acceptFriendRequest($sender); 346 | $recipients[1]->acceptFriendRequest($sender); 347 | $recipients[2]->blockFriend($sender); 348 | $this->assertCount(1, $sender->getBlockedFriendships()); 349 | } 350 | 351 | /** @test */ 352 | public function it_returns_user_friends() 353 | { 354 | $sender = createUser(); 355 | $recipients = createUser([], 4); 356 | 357 | foreach ($recipients as $recipient) { 358 | $sender->befriend($recipient); 359 | } 360 | 361 | $recipients[0]->acceptFriendRequest($sender); 362 | $recipients[1]->acceptFriendRequest($sender); 363 | $recipients[2]->denyFriendRequest($sender); 364 | 365 | $this->assertCount(2, $sender->getFriends()); 366 | $this->assertCount(1, $recipients[1]->getFriends()); 367 | $this->assertCount(0, $recipients[2]->getFriends()); 368 | $this->assertCount(0, $recipients[3]->getFriends()); 369 | 370 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getFriends()); 371 | } 372 | 373 | /** @test */ 374 | public function it_returns_user_friends_per_page() 375 | { 376 | $sender = createUser(); 377 | $recipients = createUser([], 6); 378 | 379 | foreach ($recipients as $recipient) { 380 | $sender->befriend($recipient); 381 | } 382 | 383 | $recipients[0]->acceptFriendRequest($sender); 384 | $recipients[1]->acceptFriendRequest($sender); 385 | $recipients[2]->denyFriendRequest($sender); 386 | $recipients[3]->acceptFriendRequest($sender); 387 | $recipients[4]->acceptFriendRequest($sender); 388 | 389 | 390 | $this->assertCount(2, $sender->getFriends(2)); 391 | $this->assertCount(4, $sender->getFriends(0)); 392 | $this->assertCount(4, $sender->getFriends(10)); 393 | $this->assertCount(1, $recipients[1]->getFriends()); 394 | $this->assertCount(0, $recipients[2]->getFriends()); 395 | $this->assertCount(0, $recipients[5]->getFriends(2)); 396 | 397 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getFriends()); 398 | } 399 | 400 | /** @test */ 401 | public function it_returns_user_friends_of_friends() 402 | { 403 | $sender = createUser(); 404 | $recipients = createUser([], 2); 405 | $fofs = createUser([], 5)->chunk(3); 406 | 407 | foreach ($recipients as $recipient) { 408 | $sender->befriend($recipient); 409 | $recipient->acceptFriendRequest($sender); 410 | 411 | //add some friends to each recipient too 412 | foreach ($fofs->shift() as $fof) { 413 | $recipient->befriend($fof); 414 | $fof->acceptFriendRequest($recipient); 415 | } 416 | } 417 | 418 | $this->assertCount(2, $sender->getFriends()); 419 | $this->assertCount(4, $recipients[0]->getFriends()); 420 | $this->assertCount(3, $recipients[1]->getFriends()); 421 | 422 | $this->assertCount(5, $sender->getFriendsOfFriends()); 423 | 424 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getFriendsOfFriends()); 425 | } 426 | 427 | /** @test */ 428 | public function it_returns_user_mutual_friends() 429 | { 430 | $sender = createUser(); 431 | $recipients = createUser([], 2); 432 | $fofs = createUser([], 5)->chunk(3); 433 | 434 | foreach ($recipients as $recipient) { 435 | $sender->befriend($recipient); 436 | $recipient->acceptFriendRequest($sender); 437 | 438 | //add some friends to each recipient too 439 | foreach ($fofs->shift() as $fof) { 440 | $recipient->befriend($fof); 441 | $fof->acceptFriendRequest($recipient); 442 | $fof->befriend($sender); 443 | $sender->acceptFriendRequest($fof); 444 | } 445 | } 446 | 447 | $this->assertCount(3, $sender->getMutualFriends($recipients[0])); 448 | $this->assertCount(3, $recipients[0]->getMutualFriends($sender)); 449 | 450 | $this->assertCount(2, $sender->getMutualFriends($recipients[1])); 451 | $this->assertCount(2, $recipients[1]->getMutualFriends($sender)); 452 | 453 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getMutualFriends($recipients[0])); 454 | } 455 | 456 | /** @test */ 457 | public function it_returns_user_mutual_friends_per_page() 458 | { 459 | $sender = createUser(); 460 | $recipients = createUser([], 2); 461 | $fofs = createUser([], 8)->chunk(5); 462 | 463 | foreach ($recipients as $recipient) { 464 | $sender->befriend($recipient); 465 | $recipient->acceptFriendRequest($sender); 466 | 467 | //add some friends to each recipient too 468 | foreach ($fofs->shift() as $fof) { 469 | $recipient->befriend($fof); 470 | $fof->acceptFriendRequest($recipient); 471 | $fof->befriend($sender); 472 | $sender->acceptFriendRequest($fof); 473 | } 474 | } 475 | 476 | $this->assertCount(2, $sender->getMutualFriends($recipients[0], 2)); 477 | $this->assertCount(5, $sender->getMutualFriends($recipients[0], 0)); 478 | $this->assertCount(5, $sender->getMutualFriends($recipients[0], 10)); 479 | $this->assertCount(2, $recipients[0]->getMutualFriends($sender, 2)); 480 | $this->assertCount(5, $recipients[0]->getMutualFriends($sender, 0)); 481 | $this->assertCount(5, $recipients[0]->getMutualFriends($sender, 10)); 482 | 483 | $this->assertCount(1, $recipients[1]->getMutualFriends($recipients[0], 10)); 484 | 485 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getMutualFriends($recipients[0], 2)); 486 | } 487 | 488 | /** @test */ 489 | public function it_returns_user_mutual_friends_number() 490 | { 491 | $sender = createUser(); 492 | $recipients = createUser([], 2); 493 | $fofs = createUser([], 5)->chunk(3); 494 | 495 | foreach ($recipients as $recipient) { 496 | $sender->befriend($recipient); 497 | $recipient->acceptFriendRequest($sender); 498 | 499 | //add some friends to each recipient too 500 | foreach ($fofs->shift() as $fof) { 501 | $recipient->befriend($fof); 502 | $fof->acceptFriendRequest($recipient); 503 | $fof->befriend($sender); 504 | $sender->acceptFriendRequest($fof); 505 | } 506 | } 507 | 508 | $this->assertEquals(3, $sender->getMutualFriendsCount($recipients[0])); 509 | $this->assertEquals(3, $recipients[0]->getMutualFriendsCount($sender)); 510 | 511 | $this->assertEquals(2, $sender->getMutualFriendsCount($recipients[1])); 512 | $this->assertEquals(2, $recipients[1]->getMutualFriendsCount($sender)); 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /tests/Stub_User.php: -------------------------------------------------------------------------------- 1 | append_config(array( 4 | 'Hootlex\Friendships\FriendshipsServiceProvider' 5 | )), 6 | ); -------------------------------------------------------------------------------- /tests/config/database.php: -------------------------------------------------------------------------------- 1 | 'mysql', 4 | 'connections' => array( 5 | 'mysql' => [ 6 | 'driver' => 'mysql2', 7 | 'host' => '127.0.0.1', 8 | 'database' => 'friendships_test', 9 | 'username' => 'travis', 10 | 'password' => '', 11 | 'charset' => 'utf8', 12 | 'collation' => 'utf8_unicode_ci', 13 | 'prefix' => '', 14 | 'strict' => false, 15 | ] 16 | ), 17 | ); 18 | 19 | -------------------------------------------------------------------------------- /tests/config/friendships.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'fr_pivot' => 'friendships', 7 | 'fr_groups_pivot' => 'user_friendship_groups' 8 | ], 9 | 10 | 'groups' => [ 11 | 'acquaintances' => 0, 12 | 'close_friends' => 1, 13 | 'family' => 2 14 | ] 15 | 16 | ]; -------------------------------------------------------------------------------- /tests/helpers.php: -------------------------------------------------------------------------------- 1 | create($overrides); 12 | if (count($users) == 1) { 13 | return $users->first(); 14 | } 15 | return $users; 16 | } 17 | --------------------------------------------------------------------------------