edulazaro/larallow

Roles and permissions support for Laravel

Installs: 2 264

Dependents: 0

Suggesters: 0

Security: 0

Stars: 65

Watchers: 0

Forks: 1

Open Issues: 1

pkg:composer/edulazaro/larallow

1.1.1 2025-11-27 03:25 UTC

This package is auto-updated.

Last update: 2025-11-27 03:30:53 UTC


README

Total Downloads Latest Stable Version

Larallow is a flexible Laravel package for managing roles and permissions with advanced features including scoped roles and permissions, polymorphic relations, translation support, and seamless integration with PHP enums for permissions. Zero configuration required.

Why this package when Spatie Permissions exists?

Spatie Permissions is a great package. However it stores permissions in the database by default and does not handle well scopes or permissions for different actors, also requiring to specify the guard for each permission. It's all about your preferences and project requirements.

Features

  • Manage roles and permissions for any actor model (User, Client, etc.)
  • Support for scoped roles via polymorphic roleable models (e.g., specific projects, teams)
  • Support for scoped permissions via polymorphic permissionable models (e.g., specific resources)
  • Define permissions with a fluent API in a similar way you define Laravel routes
  • Fluent querying and checking with Permissions and Roles helper classes
  • Built-in translation support for role names without external packages
  • Permission hierarchy (implications)

Installation

Add the package via Composer:

composer require edulazaro/larallow

Publish migrations and run them:

php artisan vendor:publish --tag=larallow
php artisan migrate

Quick Start

1. Add traits to your model

use EduLazaro\Larallow\Concerns\HasPermissions;
use EduLazaro\Larallow\Concerns\HasRoles;

class User extends Authenticatable
{
    use HasPermissions, HasRoles;
}

2. Define permissions (in a service provider)

use EduLazaro\Larallow\Permission;

Permission::create([
    'edit-post' => 'Edit Post',
    'delete-post' => 'Delete Post',
])->for(User::class);

3. Assign and check permissions

// Assign a permission
$user->allow('edit-post');

// Or using the permissions() method
$user->permissions('edit-post')->allow();

// Check if user has permission (direct or via role)
$user->permissions('edit-post')->check();

// Check direct permission only
$user->hasPermission('edit-post');

4. Work with roles

use EduLazaro\Larallow\Models\Role;

// Create a role
$role = Role::create(['handle' => 'editor', 'name' => 'Editor']);

// Add permissions to the role
$role->addPermission('edit-post');

// Assign role to user
$user->assignRole($role);

// Check role
$user->hasRole('editor');

Setup

Actor Traits

Add the HasPermissions and HasRoles traits to your actor models (User, Client, etc.):

use EduLazaro\Larallow\Concerns\HasPermissions;
use EduLazaro\Larallow\Concerns\HasRoles;

class User extends Authenticatable
{
    use HasPermissions, HasRoles;
}

You can use only one trait if you don't need both features.

Role Tenant Trait

For models that own roles (e.g., Account, Group, Organization), add the IsRoleTenant trait:

use EduLazaro\Larallow\Concerns\IsRoleTenant;

class Account extends Model
{
    use IsRoleTenant;
}

This allows you to retrieve roles belonging to that tenant:

$roles = $account->roles()->get();

Morph Maps (Recommended)

For consistent and secure morph relationships, define all models in your morph map:

use Illuminate\Database\Eloquent\Relations\Relation;

// In AppServiceProvider boot()
Relation::morphMap([
    'user' => User::class,
    'client' => Client::class,
    'office' => Office::class,
]);

This is optional but heavily recommended, especially when dealing with multiple actor types or scopes.

Permissions

Defining Permissions

Register permissions using Permission::create() in a service provider:

use EduLazaro\Larallow\Permission;

// Single permission
Permission::create('edit-post')->label('Edit Post');

// Multiple permissions
Permission::create([
    'edit-post' => 'Edit Post',
    'delete-post' => 'Delete Post',
    'view-post' => 'View Post',
]);

For specific actor types

Use for() to restrict permissions to specific actor models:

Permission::create('edit-post')->for(User::class);
Permission::create('manage-account')->for([User::class, Client::class]);

// Using morph map names
Permission::create('edit-post')->for('user');

For specific scopes

Use on() to restrict permissions to specific scope models (e.g., Office, Group):

Permission::create('manage-office')
    ->for(User::class)
    ->on(Office::class);

// Multiple scopes
Permission::create('manage-resources')
    ->for(User::class)
    ->on([Office::class, Group::class]);

Grouping permissions

Define multiple permissions with shared configuration:

Permission::create([
    'manage-clients' => 'Manage Clients',
    'manage-properties' => 'Manage Properties',
    'manage-appointments' => 'Manage Appointments',
])->for(User::class)
  ->on([Office::class, Group::class]);

Permission implications (hierarchy)

Define permission hierarchies where higher-level permissions imply lower-level ones:

Permission::create('manage-posts')
    ->for(User::class)
    ->implies('edit-post');

Permission::create('edit-post')->for(User::class);

// Users with 'manage-posts' automatically have 'edit-post'

Using enums

Permission::create(UserPermission::EditPost->value)->label('Edit Post');

Permission::create([
    UserPermission::EditPost->value => 'Edit Post',
    UserPermission::DeletePost->value => 'Delete Post',
])->for(User::class);

Translating permission labels

Permission::create([
    'edit-post' => __('Edit Post'),
]);

Or for easier text management you can use Laratext package:

Permission::create([
    'edit-post' => text('edit_post', 'Edit Post'),
]);

Assigning Permissions

Assign permissions directly to actors:

// Single permission
$user->allow('edit-post');
$user->allow(UserPermission::EditPost);

// Multiple permissions
$user->allow(['edit-post', 'delete-post']);

With scope

$office = Office::find(1);
$user->allow('manage-office', $office);

Alternative syntax

$user->permissions('edit-post')->allow();
$user->permissions(['edit-post', 'delete-post'])->allow();

Removing Permissions

$user->deny('edit-post');

// With scope
$user->deny('manage-office', $office);

// Alternative
$user->permissions('edit-post')->deny();

Syncing Permissions

Sync permissions to match a given list (adds missing, removes others):

$user->syncPermissions(['edit-post', 'view-post']);

// With scope
$user->syncPermissions(['manage-office'], $office);

Checking Permissions

Direct permissions only

Check if user has a permission directly assigned (not via roles):

$user->hasPermission('edit-post');
$user->hasPermission('edit-post', $scope);

// Multiple (all required)
$user->hasPermissions(['edit-post', 'delete-post']);

You can also retrieve the list of permissions directly assigned to an actor:

$permissions = $user->permissions; // Collection of ActorPermission models

foreach ($permissions as $permission) {
    echo $permission->permission; // string permission name
}

// Query specific permissions
$canEdit = $user->permissions()->where('permission', 'edit-post')->exists();

Combined (direct + role-based)

Check if user has permission either directly or via a role:

// Model method (simplest)
$user->permissions('edit-post')->check();

// With scope
$user->permissions('edit-post')->on($office)->check();

// Helper function
permissions('edit-post')->for($user)->check();

// Permissions class (verbose)
Permissions::query()
    ->permissions('edit-post')
    ->for($user)
    ->check();

Check any vs all

// True if user has ANY of these permissions
$user->permissions(['edit-post', 'delete-post'])->check();

// True only if user has ALL permissions
$user->permissions(['edit-post', 'delete-post'])->checkAll();

// With scope
$user->permissions(['edit-post', 'delete-post'])->on($office)->checkAll();

Blade directives

@permissions('edit-post')
    <button>Edit Post</button>
@endpermissions

@allpermissions(['edit-post', 'delete-post'])
    <button>Manage Post</button>
@endallpermissions

// With scope
@permissions('edit-post', $scope)
    <button>Edit Post</button>
@endpermissions

Roles

Creating Roles

use EduLazaro\Larallow\Models\Role;

$role = Role::create([
    'handle' => 'editor',
    'name' => 'Editor',
]);

// With tenant and scope restrictions
$role = new Role();
$role->handle = 'office_manager';
$role->name = 'Office Manager';
$role->tenant_type = $tenant->getMorphClass();
$role->tenant_id = $tenant->id;
$role->actor_type = 'user';
$role->scope_type = 'office';
$role->save();

Assigning Roles

$user->assignRole($role);
$user->assignRole($roleId);

// Multiple roles
$user->assignRoles([$role1, $role2, $roleId]);

// With scope
$user->assignRole($role, $office);
$user->assignRoles([$role1, $role2], $office);

With tenant (multi-tenancy)

Roles::query()
    ->roles($roles)
    ->for($user)
    ->on($office)
    ->tenant($group)
    ->assign();

Removing Roles

$user->removeRole($role);
$user->removeRole($role, $scope);

Syncing Roles

Sync roles to match a given list:

$user->syncRoles([$role1, $role2]);
$user->syncRoles([1, 2, $roleInstance]);

// With scope (only affects roles for that scope)
$user->syncRoles([$managerRole], $office);

Checking Roles

$user->hasRole('editor');
$user->hasRole('editor', $scope);

You can also retrieve the roles assigned to an actor:

$roles = $user->roles; // Collection of Role models

foreach ($roles as $role) {
    echo $role->handle;
}

Check any vs all

// Any of these roles
$user->roles(['editor', 'admin'])->check();

// All of these roles
$user->roles(['editor', 'admin'])->checkAll();

// With scope
$user->roles(['editor', 'admin'])->on($scope)->checkAll();

// Using helper
roles(['editor', 'admin'])->for($user)->check();

// Using Roles class (verbose)
Roles::query()
    ->roles(['editor', 'admin'])
    ->for($user)
    ->check();

Blade directives

@roles(['admin', 'editor'])
    <p>You have elevated access.</p>
@endroles

@allroles(['admin', 'editor'])
    <p>You have full admin/editor access.</p>
@endallroles

Role Permissions

Add permissions to roles:

$role->addPermission('edit-post');
$role->addPermission(['edit-post', 'delete-post', 'view-post']);

Remove permissions from roles:

$role->removePermission('edit-post');
$role->removePermission(['edit-post', 'delete-post']);

You can also use the relationship directly:

$role->permissions()->create(['permission' => 'edit-post']);
$role->permissions()->where('permission', 'edit-post')->delete();

Checking Role Permissions

Check if user has permissions through their roles (ignoring direct permissions):

// Single permission
$user->hasRolePermission('edit-post');
$user->hasRolePermission('edit-post', $scope);

// All required
$user->hasRolePermissions(['edit-post', 'delete-post']);

// Any of these
$user->hasAnyRolePermission('edit-post');
$user->hasAnyRolePermissions(['edit-post', 'delete-post']);

Query Scopes

Filter actors by their permissions:

// Users with a specific permission (direct or via role)
$users = User::withPermission('edit-post')->get();
$users = User::withPermission('edit-post', $office)->get();

// Users with any of these permissions
$users = User::withAnyPermission(['edit-post', 'delete-post'])->get();

// Users with all of these permissions
$users = User::withAllPermissions(['edit-post', 'delete-post'])->get();

These scopes automatically include implied permissions.

Advanced

Permission Query Builder

Query registered permissions:

// Get all permissions
$allPermissions = Permission::all();

// Get by handle
$permission = Permission::get('edit-post');

// Check existence
Permission::exists('edit-post');

// Check if allowed for actor/scope
Permission::isAllowedFor('edit-post', 'user', 'office');

Filtering permissions

$permissions = Permission::query()
    ->where('actor_type', 'user')
    ->where('scope_type', 'office')
    ->get();

// Alternative methods
$permissions = Permission::query()
    ->whereActorType('user')
    ->whereScopeType('office')
    ->get();

// Get as options array [handle => label]
$options = Permission::query()
    ->whereActorType('user')
    ->options();

// Pluck specific fields
$options = Permission::query()
    ->where('actor_type', 'user')
    ->pluck('label', 'handle')
    ->toArray();

// Filter by multiple handles
$permissions = Permission::query()
    ->where('handle', ['edit-post', 'delete-post'])
    ->get();

// Get first match
$permission = Permission::query()
    ->whereActorType('user')
    ->first();

// Short form syntax
$permissions = Permission::where('actor_type', 'user')
    ->where('scope_type', 'office')
    ->get();

$permission = Permission::where('actor_type', 'user')->first();

Multi-tenancy

The tenant() method scopes role operations to a specific tenant:

$tenant = Group::find(1);

Roles::query()
    ->roles($roles)
    ->for($user)
    ->on($office)
    ->tenant($tenant)
    ->assign();

During assignment, an InvalidArgumentException is thrown if any role does not belong to the specified tenant.

Deleting Roles

$role = Role::find($roleId);
$role->delete();

Make sure to detach any assignments or permissions related to this role before deleting to maintain data integrity.

Translation Support

Roles support multilingual translations without external packages:

// Get translated name (uses current locale)
echo $role->name;

// Get specific locale with fallback
$name = $role->getTranslation('name', 'es', 'Default Name');

// Set translation
$role->setTranslation('name', 'fr', 'Nom en Français');
$role->save();

Testing

Run the package tests with:

./vendor/bin/phpunit

Contributing

Contributions are welcome! Please fork the repo, add tests, and submit a PR.

License

Larallow is open-sourced software licensed under the MIT license.