diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f76760d --- /dev/null +++ b/composer.json @@ -0,0 +1,13 @@ +{ + "name": "waiyanhein/lara-role-manager", + "description": "Library to manage the user roles for Laravel", + "license": "MIT", + "authors": [ + { + "name": "Wai Yan Hein", + "email": "waiyanhein.uk@gmail.com" + } + ], + "minimum-stability": "dev", + "require": {} +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..9071079 --- /dev/null +++ b/readme.md @@ -0,0 +1,54 @@ +###Package to manage user roles for Laravel application + +Having to write code for managing user roles each time you create a new Laravel project is a repeating project. Sometimes, you will try to copy the existing code form the other projects, such as Role model class. +You will need to create a model class for role, plus, migration file for it. Then you also have to define the relationship between user and role. Then you will have to write methods for related logic for example, checking if the user belongs to a role. This package includes all the necessary common logic and components fo handling user roles for Laravel application. + + +####installation + +- `composer require waiyanhein/lara-role-manager` + +####Publishing config file to define roles + +- `php artisan vendor:publish` + +Then roles.php file will be published under the app/config folder. You can define the roles in the following format. + +` +[ + 1 => 'Superadmin', + 2 => 'Manager', + 3 => 'Staff' +] +` +The array key is the unique code and the value is the title to be displayed to the end users. + +####Migrating the table +- Then you migrate the tables for the roles. `php artisan migrate` + +####Usages + +You will need to place the `Waiyanhein\LaraRoleManager\Traits\RoleManager` trait into the user model class. + +####Seeding data (Optional) +If you are seeding the roles into the database, you can seed the data from the `config/roles.php` calling the following method in your seeder class. +- `\Waiyanhein\LaraRoleManager\LaraRolesSeeder::seed();` + +####Methods +Then you can use the following methods based on your need. + +`Waiyanhein\LaraRoleManager\Models\Role` class + +- `findRole($code)` - static method. This method will return the role based on the code. $code is the unique code defined in the `config/roles.php` file mapped to the displayed title. +- `users()` - This method is the Eloquent relationship. You can leverage all the features of Eloquent on this method. For example, `$role->users()->get()`. +- `roles()` - static method. This method will return all the roles in the `id` => `displayed title` mapping. Return type is array. + +`Waiyanhein\LaraRoleManager\Traits\RoleManager` trait + +You embed this trait into your user model class so that your user model class can leverage all the features of this trait. + +- `roles()` - This method is the Eloquent relationship. You can leverage all the features of Eloquent on this method. For example: `$user->roles()->get()`. +- `attachRole($code)` - This will attach a role to the user. $code is the unique code defined in the `config/roles.php` file mapped to the displayed title. For example: `$user->attachRole(User::ROLE_ADMIN)`; +- `attachRoles($codes)` - This is similar to `attachRole` function. Instead, you pass an array of codes to assign multiple roles to the user at the same time. +- `hasRole($code)` - Checks if the user has a role. This method will return boolean value. For example: `$user->hasRole(User::ROLE_ADMIN)`. +- `hasRoles($codes)`. This method is very similar to `hasRole` method. Instead, this will check if the user belongs to all the roles which are pass as an array to the method. If any of the role is missing, it will return false. diff --git a/src/LaraRoleManagerServiceProvider.php b/src/LaraRoleManagerServiceProvider.php new file mode 100644 index 0000000..d0484db --- /dev/null +++ b/src/LaraRoleManagerServiceProvider.php @@ -0,0 +1,32 @@ +publishes([ + __DIR__ . '/roles.php' => config_path('roles.php'), + ]); + $this->loadMigrationsFrom(__DIR__ . '/migrations'); + $this->loadFactoriesFrom(__DIR__ . '/factories'); + } +} diff --git a/src/LaraRolesSeeder.php b/src/LaraRolesSeeder.php new file mode 100644 index 0000000..bd95726 --- /dev/null +++ b/src/LaraRolesSeeder.php @@ -0,0 +1,20 @@ + $displayTitle) { + factory(Role::class)->create([ + 'code' => $code, + 'display_title' => $displayTitle, + ]); + } + } +} diff --git a/src/factories/RoleFactory.php b/src/factories/RoleFactory.php new file mode 100644 index 0000000..704a97d --- /dev/null +++ b/src/factories/RoleFactory.php @@ -0,0 +1,23 @@ +define(\Waiyanhein\LaraRoleManager\Models\Role::class, function (Faker $faker) { + return [ + 'code' => $faker->unique()->randomNumber(3), + 'display_title' => $faker->unique()->word, + ]; +}); diff --git a/src/migrations/2020_06_12_161325_create_roles_table.php b/src/migrations/2020_06_12_161325_create_roles_table.php new file mode 100644 index 0000000..f5dac8a --- /dev/null +++ b/src/migrations/2020_06_12_161325_create_roles_table.php @@ -0,0 +1,33 @@ +id(); + $table->integer('code')->unique(); + $table->string('display_title')->unique(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('roles'); + } +} diff --git a/src/migrations/2020_06_12_190554_create_user_role_table.php b/src/migrations/2020_06_12_190554_create_user_role_table.php new file mode 100644 index 0000000..8a66355 --- /dev/null +++ b/src/migrations/2020_06_12_190554_create_user_role_table.php @@ -0,0 +1,36 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->unsignedBigInteger('role_id'); + $table->timestamps(); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_role'); + } +} diff --git a/src/models/Role.php b/src/models/Role.php new file mode 100644 index 0000000..ff9025f --- /dev/null +++ b/src/models/Role.php @@ -0,0 +1,29 @@ +first(); + } + + public function users() + { + $this->belongsToMany(config('auth.providers.users.model'), 'user_role', 'role_id', 'user_id')->withTimestamps(); + } + + public static function roles() + { + $roles = [ ]; + $roleModels = static::all(); + foreach ($roleModels as $roleModel) { + $roles[$roleModel->id] = $roleModel->display_title; + } + + return $roles; + } +} diff --git a/src/roles.php b/src/roles.php new file mode 100644 index 0000000..175fa18 --- /dev/null +++ b/src/roles.php @@ -0,0 +1,13 @@ + 'Super admin', + 2 => 'Manager', + 3 => 'Staff', + ] + */ +]; diff --git a/src/traits/RoleManager.php b/src/traits/RoleManager.php new file mode 100644 index 0000000..fcbb525 --- /dev/null +++ b/src/traits/RoleManager.php @@ -0,0 +1,51 @@ +belongsToMany(\Waiyanhein\LaraRoleManager\Models\Role::class, 'user_role', 'user_id', 'role_id')->withTimestamps(); + } + + public function attachRole($code) + { + $role = Role::where('code', $code)->first(); + + if (! $role) { + throw new \LogicException("Invalid code passed to attachRole function. Code: $code"); + } + + $this->roles()->attach([ $role->id ]); + } + + public function attachRoles($codes) + { + $validCodes = [ ]; + foreach ($codes as $code) { + if (! $this->hasRole($code)) { + $validCodes[] = $code; + } + } + $roleIds = Role::whereIn('code', $validCodes)->get()->pluck('id')->all(); + + if ($roleIds) { + $this->roles()->attach($roleIds); + } + } + + public function hasRole($code) + { + $role = $this->roles()->where('code', $code)->first(); + + return ($role)? true: false; + } + + public function hasRoles($codes) + { + return $this->roles()->whereIn('code', $codes)->count() == count($codes); + } +} diff --git a/tests/Waiyanhein/LaraRoleManagerTest.php b/tests/Waiyanhein/LaraRoleManagerTest.php new file mode 100644 index 0000000..bc0dae6 --- /dev/null +++ b/tests/Waiyanhein/LaraRoleManagerTest.php @@ -0,0 +1,147 @@ +create(); + $user = factory(config('auth.providers.users.model'))->create(); + + $user->attachRole($role->code); + $user->refresh(); + + $this->assertEquals(1, $user->roles()->count()); + $this->assertEquals($role->id, $user->roles()->first()->id); + } + + /** @test */ + public function attaching_a_role_to_user_throws_logic_exception_when_code_is_invalid() + { + $this->expectException(\LogicException::class); + $user = factory(config('auth.providers.users.model'))->create(); + + $user->attachRole(111); + } + + /** @test */ + public function it_can_attach_roles_to_a_user() + { + $roleAdmin = factory(Role::class)->create([ + 'code' => 11 + ]); + $roleStaff = factory(Role::class)->create([ + 'code' => 12 + ]); + + $user = factory(config('auth.providers.users.model'))->create(); + $user->attachRoles([ $roleAdmin->code, $roleStaff->code ]); + $user->refresh(); + $roles = $user->roles()->get(); + + $this->assertEquals(2, $user->roles()->count()); + $this->assertEquals($roleAdmin->id, $roles[0]->id); + $this->assertEquals($roleStaff->id, $roles[1]->id); + } + + /** @test */ + public function attaching_roles_to_user_ignore_role_if_it_is_already_attached() + { + $roleAdmin = factory(Role::class)->create([ + 'code' => 11 + ]); + $roleStaff = factory(Role::class)->create([ + 'code' => 12 + ]); + + $user = factory(config('auth.providers.users.model'))->create(); + $user->attachRoles([ $roleAdmin->code, $roleStaff->code, $roleStaff->code ]); + $user->refresh(); + + $this->assertEquals(2, $user->roles()->count()); + } + + /** @test */ + public function it_can_find_role() + { + $role = factory(Role::class)->create([ 'code' => 123 ]); + + $this->assertEquals($role->id, Role::findRole(123)->id); + } + + /** @test */ + public function has_role_returns_true_when_user_has_role() + { + $role = factory(Role::class)->create(); + $user = factory(config('auth.providers.users.model'))->create(); + + $user->attachRole($role->code); + $user->refresh(); + + $this->assertTrue($user->hasRole($role->code)); + } + + /** @test */ + public function has_role_returns_false_when_user_does_not_have_role() + { + $user = factory(config('auth.providers.users.model'))->create(); + + $this->assertFalse($user->hasRole(123)); + } + + /** @test */ + public function has_roles_returns_true_when_user_has_roles() + { + $roleAdmin = factory(Role::class)->create([ + 'code' => 11 + ]); + $roleStaff = factory(Role::class)->create([ + 'code' => 12 + ]); + + $user = factory(config('auth.providers.users.model'))->create(); + $user->attachRoles([ $roleAdmin->code, $roleStaff->code ]); + $user->refresh(); + + $this->assertTrue($user->hasRoles([ 11, 12 ])); + } + + /** @test */ + public function has_roles_returns_false_when_user_does_not_have_one_of_the_roles() + { + $roleAdmin = factory(Role::class)->create([ + 'code' => 11 + ]); + + $user = factory(config('auth.providers.users.model'))->create(); + $user->attachRoles([ $roleAdmin->code ]); + $user->refresh(); + + $this->assertFalse($user->hasRoles([ 11, 12 ])); + } + + /** @test */ + public function get_roles_returns_roles_in_right_data_format() + { + $roleAdmin = factory(Role::class)->create([ + 'code' => 11 + ]); + $roleStaff = factory(Role::class)->create([ + 'code' => 12 + ]); + + $roles = Role::roles(); + $this->assertEquals($roleAdmin->display_title, $roles[$roleAdmin->id]); + $this->assertEquals($roleStaff->display_title, $roles[$roleStaff->id]); + } +}