In this course section, let's get to a more complex example with Teams and Roles.
Scenario
Imagine a scenario for managing clinics. The application has roles in a hierarchy: master admin
> super admin
> admin
> user
.
The idea comes from a real comment on our YouTube channel:
We've tried to re-create that scenario in this demo project:
-
master admin
has access to everything -
super admin
is a clinic owner who can have multiple clinics -
admin
: a manager of each clinic -
users
: finally, "regular" users are also split into three roles:staff
,doctors
, andpatients
.
We will continue the same Task Management project as in the previous lessons and use the Spatie Permissions package with the Teams function enabled. In this case, the Team will represent a Clinic.
Here's the plan of this project:
- We define Roles and their Permissions
- We add the Teams and their CRUD Management
- We add the User Management and who has permission for it
- Then, finally, we add the Tasks CRUD with permissions
- All covered by tests, traditionally
Roles and Permissions: Enums and Seeders
First, I created an Enum file listing all the roles:
php artisan make:enum Role
app/Enums/Role.php:
namespace App\Enums; enum Role: string{ case Patient = 'patient'; case Doctor = 'doctor'; case Staff = 'staff'; case ClinicAdmin = 'clinic-admin'; case ClinicOwner = 'clinic-owner'; case MasterAdmin = 'master-admin';}
Why Enum? To avoid typos in the string names, when typing "clinic-owner" somewhere else in the code and mistyping it as "clinicowner" or "clinic_owner". So, we will only reference role names via their Enum values. Consistency.
Similarly, Enum for Permissions.
app/Enums/Permission.php:
namespace App\Enums; enum Permission: string{ case LIST_TEAM = 'list-team'; case CREATE_TEAM = 'create-team'; case LIST_USER = 'list-user'; case CREATE_USER = 'create-user'; case LIST_TASK = 'list-task'; case CREATE_TASK = 'create-task'; case EDIT_TASK = 'edit-task'; case DELETE_TASK = 'delete-task'; case SWITCH_TEAM = 'switch-team';}
Next, we immediately use that Enum in practice: we seed all those roles and permissions into DB.
php artisan make:seeder RoleAndPermissionSeeder
Now, which role can do what?
Here's the table I came up with as I understand the roles in a typical clinic:
All that role/permission list is available in this seeder's private method syncPermissionsToRole($role)
.
database/seeders/RoleAndPermissionSeeder.php:
use App\Enums\Permission;use App\Enums\Role as RoleEnum;use Illuminate\Database\Seeder;use Spatie\Permission\Models\Role; class RoleAndPermissionSeeder extends Seeder{ public function run(): void { foreach (Permission::cases() as $permission) { \Spatie\Permission\Models\Permission::create(['name' => $permission->value]); } foreach (RoleEnum::cases() as $role) { $role = Role::create(['name' => $role->value]); $this->syncPermissionsToRole($role); } } private function syncPermissionsToRole(Role $role): void { $permissions = []; switch ($role->name) { case RoleEnum::MasterAdmin->value: $permissions = [ Permission::LIST_TEAM, Permission::CREATE_TEAM, ]; break; case RoleEnum::ClinicOwner->value: $permissions = [ Permission::SWITCH_TEAM, Permission::LIST_USER, Permission::CREATE_USER, ]; break; case RoleEnum::ClinicAdmin->value: $permissions = [ Permission::LIST_USER, Permission::CREATE_USER, Permission::LIST_TASK, Permission::CREATE_TASK, Permission::EDIT_TASK, Permission::DELETE_TASK, ]; break; case RoleEnum::Staff->value: $permissions = [ Permission::LIST_TASK, Permission::CREATE_TASK, Permission::EDIT_TASK, Permission::DELETE_TASK, ]; break; case RoleEnum::Doctor->value: $permissions = [ Permission::LIST_TASK, Permission::CREATE_TASK, Permission::EDIT_TASK, ]; break; case RoleEnum::Patient->value: $permissions = [ Permission::LIST_TASK, ]; break; } $role->syncPermissions($permissions); }}
Notice: To lower the scope of this already huge tutorial, we will build just the list and create features for Teams/Users without edit/delete functionality. For Tasks, it will be the full CRUD.
Next, we add this seeder to the main DatabaseSeeder file:
database/seeders/DatabaseSeeder.php
class DatabaseSeeder extends Seeder{ public function run(): void { $this->call([ RoleAndPermissionSeeder::class, ]); }}
After running this seeder, we have this data in the DB, according to the spatie/laravel-permission
structure:
Roles:
Permissions:
Role_has_permissions:
Also, we will immediately add Seeder to the Pest tests. We will write those tests later, but I immediately feel all test methods will rely on the roles/permissions already existing in the DB.
tests/Pest.php
pest()->extend(Tests\TestCase::class) ->use(Illuminate\Foundation\Testing\RefreshDatabase::class) ->beforeEach(function () { \Pest\Laravel\seed(\Database\Seeders\RoleAndPermissionSeeder::class); }) ->in('Feature');
Great, we have the roles/permissions DB structure ready. Next step is to build the DB structure for teams and users.
No comments yet…