richard-roman/laravel-dynamic-access

Dynamic multi-tenant access management on top of Spatie Laravel Permission

Maintainers

Package info

github.com/Richard-Roman/laravel-dynamic-access

pkg:composer/richard-roman/laravel-dynamic-access

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-07-12 17:16 UTC

This package is auto-updated.

Last update: 2026-07-12 17:24:52 UTC


README

Latest Version on Packagist Total Downloads Build Status

A robust, portable, and secure Laravel package to manage dynamic access permissions on top of Spatie Laravel Permission. Tailored for multi-tenant applications, it allows granular access control mapped dynamically through roles, modules, and companies.

Key Features

  • Dynamic Access Mapping: Manage roles, modules (core.modulos), actions (iam.acciones), and access matrices (iam.accesos) dynamically.
  • Spatie Permission Integration: Syncs access matrices to Spatie permissions automatically via a dynamic, database-driven builder.
  • Multi-Tenant Design: Uses a customizable TenantResolver to isolate permissions per company/team in real-time.
  • Artisan Reconciliation: Quick sync and clean-up command (php artisan access:reconcile) to keep the DB and Spatie cache aligned.
  • Packagist-Ready: Fully decoupled from the host application, with dynamic resolution of models and highly configurable routing.

Prerequisites

This package assumes your host application already has the following tables:

  • core.empresas — companies/tenants table with an id_empresa primary key
  • core.modulos — modules table (the package migration alters this table to add id_empresa, id_modulo_padre, and url columns)

Important: If core.modulos does not yet have id_empresa, publish and run the package migrations before using any module endpoints.

Installation

Install the package via Composer:

composer require richard-roman/laravel-dynamic-access

Publish the configuration file:

php artisan vendor:publish --provider="DynamicAccess\DynamicAccessServiceProvider" --tag="dynamic-access-config"

Publish and run the migrations:

php artisan vendor:publish --provider="DynamicAccess\DynamicAccessServiceProvider" --tag="dynamic-access-migrations"
php artisan migrate

The migrations will:

  1. Alter core.modulos — adding id_empresa (tenant FK), id_modulo_padre (self-reference hierarchy), and url.
  2. Create iam.acciones — action definitions per module.
  3. Create iam.accesos — the access matrix (source of truth).

Configuration

The configuration file is published at config/dynamic-access.php:

return [
    // Automatically register package API routes
    'register_routes' => true,

    // Prefix for all package routes (registered under /api/{route_prefix})
    'route_prefix' => 'access-manager',

    // Middleware group applied to all package routes
    // The package always appends 'dynamic.team' to this group automatically
    'middleware_group' => ['auth'],

    // Spatie Permission name required for write endpoints (POST/PUT/DELETE)
    // Set to null to disable this check.
    'manage_permission' => 'manage-access',

    // Authentication guard used when creating roles and permissions with Spatie
    // Change if your host uses a guard other than 'web' (e.g. 'api', 'sanctum')
    'guard_name' => 'web',

    // Customize database table names for host integration
    'tables' => [
        'modulos'  => 'core.modulos',
        'acciones' => 'iam.acciones',
        'accesos'  => 'iam.accesos',
        'empresas' => 'core.empresas',
        'roles'    => 'iam.roles',
    ],

    // Eloquent model class for roles — resolved dynamically at runtime
    'models' => [
        'role' => \App\Models\IAM\Rol::class,
    ],
];

Multi-Tenant Integration

The host application must implement and bind the DynamicAccess\Contracts\TenantResolver contract to resolve the active tenant ID at runtime.

1. Implement the Contract

namespace App\Services;

use DynamicAccess\Contracts\TenantResolver;

class AppTenantResolver implements TenantResolver
{
    public function getCurrentTenantId(): ?int
    {
        // Return your active tenant/company ID (e.g. from session, subdomain, header, etc.)
        return tenant('id') ?? auth()->user()?->id_empresa;
    }
}

2. Bind it in AppServiceProvider

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use DynamicAccess\Contracts\TenantResolver;
use App\Services\AppTenantResolver;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(TenantResolver::class, AppTenantResolver::class);
    }
}

If no binding is registered, the package falls back to a null resolver (no tenant scope). This is safe for CLI commands but will return empty results on API endpoints.

Running Tests

./vendor/bin/phpunit

Tests run against SQLite in-memory with schema emulation — no external database required.

Changelog

Please see CHANGELOG for recent changes.

Contributing

Please see CONTRIBUTING for details.

License

The MIT License (MIT). Please see License File for more information.