Skip to main content

Roles and Permissions System

This document explains the high-level, declarative syntax for defining roles and permissions in the application.

Overview

The system provides a clean, maintainable way to manage roles and permissions through:
  1. Config-based role resolution - Roles are configured in config/core.php
  2. Declarative permission assignment - Permissions are defined directly in the Role enum
  3. Automatic synchronization - A single command syncs everything to the database
  4. Platform-level abstraction - Core code doesn’t depend on specific app Role enums

Configuration

config/core.php

return [
    // App-specific permission enum (optional)
    'permissions' => \App\Enums\Permission::class,

    // App-specific role enum (optional)
    'roles' => \App\Enums\Role::class,
];

Role Definition

app/Enums/Role.php

enum Role: string implements JsonSerializable
{
    use SerializeWithLabel;

    case ADMIN = 'admin';
    case DEVELOPER = 'developer';
    case STAFF = 'staff';
    case USER = 'user';

    /**
     * Get the permissions for this role.
     *
     * Return an array of permission enum cases or use the special '*' string
     * to grant all permissions.
     *
     * @return array<\UnitEnum>|string
     */
    public function permissions(): array|string
    {
        return match ($this) {
            self::ADMIN => '*', // All permissions
            self::DEVELOPER => '*', // All permissions
            self::STAFF => [
                CorePermission::VIEW_INLY_USER_GUIDE,
                Permission::MANAGE_ORDERS,
                Permission::VIEW_REPORTS,
            ],
            self::USER => [],
        };
    }

    public function label(): string
    {
        return match ($this) {
            self::ADMIN => __('Admin'),
            self::DEVELOPER => __('Developer'),
            self::STAFF => __('Staff'),
            self::USER => __('User'),
        };
    }

    /**
     * Exclude roles from the system user editing experience.
     */
    public static function excludeFromSystemUsers(): array
    {
        return []; // Or return [self::DEVELOPER] to hide from UI
    }
}

Permission Definition

Permissions are defined in two places:

Inly\Core\Enums\CorePermission (Platform-level)

enum CorePermission: string implements JsonSerializable
{
    case VIEW_USERS = 'view_users';
    case MANAGE_USERS = 'manage_users';
    case MANAGE_WORKFLOWS = 'manage_workflows';
    case MANAGE_SYSTEM = 'manage_system';
    // ...
}

App\Enums\Permission (App-specific)

enum Permission: string implements JsonSerializable
{
    case MANAGE_ORDERS = 'manage_orders';
    case VIEW_REPORTS = 'view_reports';
    // ...
}

Syncing Roles & Permissions

The permissions:generate command handles everything automatically:
php artisan permissions:generate
This command will:
  1. ✅ Discover all permissions from CorePermission and app/Enums/Permission
  2. ✅ Create new permissions in the database
  3. ✅ Delete permissions that no longer exist in either enum
  4. ✅ Create all roles from app/Enums/Role
  5. ✅ Sync permissions to each role based on their permissions() method

Helper Functions

The platform provides helper functions for working with roles dynamically:
// Get the configured role enum class
$roleClass = role_enum(); // Returns: 'App\Enums\Role' or null

// Get all role cases
$roles = role_cases(); // Returns: array of UnitEnum instances

// Get role options for UI display
$options = role_options(); // Returns: [['value' => 'admin', 'label' => 'Admin'], ...]
$options = role_options(onlySystemUsers: true); // Excludes hidden roles

Usage in Core Code

Core code uses helper functions instead of directly referencing App\Enums\Role:
// ❌ Don't do this in core code
use App\Enums\Role;
$roles = Role::options();

// ✅ Do this instead
$roles = role_options();
For type hints where you need to accept any role enum:
// Use UnitEnum or string
public function __construct(
    public \UnitEnum|string|null $role,
) {}

Benefits

1. Declarative Syntax

Permissions are defined right where they belong - in the Role enum:
self::STAFF => [
    CorePermission::VIEW_INLY_USER_GUIDE,
    Permission::MANAGE_ORDERS,
],

2. Type Safety

All permissions are enum cases, so you get full IDE autocomplete and type checking.

3. Single Source of Truth

The permissions() method is the only place you need to look to understand what permissions a role has.

4. Automatic Cleanup

Old permissions are automatically deleted when you remove them from the enum.

5. Platform Independence

Core code doesn’t depend on specific app implementations, making it reusable across projects.

Migration from Old System

If you have an old GenerateRolesAndPermissions command:
  1. Add permissions() method to your Role enum
  2. Move permission assignments from the command to the enum
  3. Delete the old command file
  4. Run php artisan permissions:generate
Example migration: Before (in command):
Role::findByName('staff')->syncPermissions([
    'view_inly_user_guide',
    'manage_orders',
]);
After (in Role enum):
self::STAFF => [
    CorePermission::VIEW_INLY_USER_GUIDE,
    Permission::MANAGE_ORDERS,
],