Skip to content

Next-gen: javaabu/permissions v3.0 #14

Open

Description

@n4ff4h

New major of the spatie/laravel-permission wrapper. It's the RBAC foundation every next-gen package with controllers relies on, so its permission-registration and policy-override APIs come first; then views and controllers for roles and permissions.

Package: javaabu/permissions · Target: v3.0 (from v2.1) · Wave: 1
Depends on: javaabu/forms, @javaabu/forms
Optional: javaabu/translatable

Built to the next-gen general rules: CI with test and coverage badges, full docs for people and agents, Laravel Boost guideline and skill, Javaabu codestyle, PHP 8.4 / Laravel 12+ (Laravel packages) or TypeScript (JS packages), Dhivehi and RTL, Blade and Inertia React for views, overridable controllers and policies with javaabu/permissions.

Checklist

Developed by an agent orchestrated from the Javaabu next-gen plan. Cross-package requests go in the dependency's repo with the cross-package label.

Activity

  1. added
    next-genNext-gen Javaabu packages
    epicTracks a whole package
    on Nov 4, 2026
  2. n4ff4h commented on Nov 4, 2026

    @n4ff4h
    CollaboratorAuthor

    Implementation plan (v3.0)

    All work is on the next-gen branch (branch alias dev-next-gen → 3.x-dev); one PR into main at the end.

    Architecture

    • Javaabu\Permissions\Permissions becomes a facade over a singleton PermissionRegistry. Packages call it from their service provider's boot(); nothing touches the database until you sync.
    • Registry: holds permission groups (group key = the existing permissions.model column), each with a label, permissions (name => English description), optional guards and default role grants. It also holds the default role definitions and package policies.
    • Sync: PermissionSyncer + php artisan permissions:sync (--prune, --guard=) + Javaabu\Permissions\Database\Seeders\PermissionsSeeder. It is idempotent: it creates or updates each permission for each guard, creates roles, and grants defaults. Grants only add permissions, except '*' roles, which are synced to everything. --prune removes registered-group permissions that are no longer registered.
    • Policies: Permissions::policy(Model::class, Policy::class). These are applied to the Gate after boot, and only where no policy is already defined. The order is: config('permissions.policies') wins, then the app's own Gate::policy(), then the package default. ResourcePolicy is the base policy: it maps viewAny/view/create/update/delete/restore/forceDelete/viewTrash to view_x/edit_x/delete_x/force_delete_x, supports custom abilities through an $abilities map, and supports optional ownership (edit_others_x).
    • Multi-auth: registered permissions go to every guard in config('permissions.guards') (default: the app's default guard), unless a group names its own guards. Helpers: Permissions::guardFor($user), Permissions::check($permissions, $guard), $user->anyPermission($permissions, $guard), @anypermission($permissions, $guard), Role::forGuard(). No App\Models\User or web assumptions; events take any Model as the causer.
    • Translations: descriptions and labels are English strings used as translation keys (__() at display time, through translated_description and group_label), so a package ships them in its lang/dv.json.
    • Controllers (Overridable controllers, policies and default permissions (RBAC, multi-auth) #11): RolesController (web and JSON API), bound through config('permissions.controllers'). Routes are registered by Route::roles([...guard, prefix, middleware, name]). RolePolicy and the roles permissions are registered through the API above. Views render Blade or Inertia depending on config('permissions.views').

    Public API sketch

    Permissions::resource('exports', 'exports', label: 'Exports');           // view/edit/delete/force_delete_exports
    Permissions::register('exports', ['download_exports' => 'Download exports'])
        ->label('Exports')
        ->guards(['web_admin'])
        ->grantTo('admin');
    Permissions::role('admin', 'Administrator', permissions: ['view_*']);
    Permissions::policy(Export::class, ExportPolicy::class);
    
    class ExportPolicy extends ResourcePolicy
    {
        protected string $resource = 'exports';
    }

    Order of work

    1. Foundations: PHP 8.4 / Laravel 12+, CI matrix, coverage and codestyle #3 foundations: versions, Pint with Javaabu style, CI matrix, Codecov, badges.
    2. Permission registration API for packages #4 registry and sync, Overridable policies #5 policies, Multi-auth support #6 multi-auth. Then push, and post an API summary here for other packages.
    3. Overridable controllers, policies and default permissions (RBAC, multi-auth) #11 controllers, routes, API.
    4. Dhivehi localization and RTL #9 lang en/dv, Documentation for people and agents #7 docs + llms.txt + upgrading guide, Laravel Boost guideline and skill #8 Boost guideline and skill.
    5. Blade (javaabu/forms) and Inertia React (@javaabu/forms) views #10 views (Blade with forms v2, React with @javaabu/forms through manifest). This waits on those packages.
    6. Optional javaabu/translatable support #12 stays blocked until translatable v3's contract exists.

    Decisions and open questions

    • Dropping the hard dependencies on javaabu/activitylog and javaabu/helpers. activitylog v2 (wave 4) will depend on permissions, and helpers requires javaabu/settings ^1.7, while settings v2 (wave 3) will also depend on permissions. Keeping either dependency would make apps on the next-gen stack unresolvable.
      • Activity logging of role/permission changes becomes optional: it is feature-detected through spatie/laravel-activitylog.
      • Role no longer implements AdminModel; it gets its own search scope.
      • Both changes go in docs/upgrading.md.
      • Open question: should Role keep AdminModel through an optional subclass?
    • Factories move from the global Database\Factories namespace to Javaabu\Permissions\Database\Factories, so they don't collide with the app's.
    • HasRoles::updateRole() no longer calls get_setting('default_role'). It takes an explicit default role and causer.
  3. n4ff4h commented on Nov 4, 2026

    @n4ff4h
    CollaboratorAuthor

    Permission and policy registration API is on next-gen

    This is for every next-gen package with controllers. Require it like this:

    "repositories": [{ "type": "vcs", "url": "https://github.com/Javaabu/permissions" }],
    "require": { "javaabu/permissions": "^3.0@dev" }

    The branch alias is dev-next-gen → 3.x-dev.

    Registering from a package

    Do this in your service provider's boot(). Nothing touches the database until the app runs the sync.

    use Javaabu\Exports\Models\Export;
    use Javaabu\Exports\Policies\ExportPolicy;
    use Javaabu\Permissions\Permissions;
    
    public function boot(): void
    {
        // view_exports, edit_exports, delete_exports, force_delete_exports
        // with descriptions "View exports", "Edit exports", …
        Permissions::resource('exports', 'exports', label: 'Exports');
    
        // extra permissions in the same group (name => English description)
        Permissions::register('exports', [
            'download_exports' => 'Download exports',
        ]);
    
        // the default policy; the app can override it
        Permissions::policy(Export::class, ExportPolicy::class);
    }
    namespace Javaabu\Exports\Policies;
    
    use Javaabu\Permissions\Policies\ResourcePolicy;
    
    class ExportPolicy extends ResourcePolicy
    {
        protected string $resource = 'exports';
    
        protected array $abilities = [
            'download' => 'download_exports',
        ];
    }

    ResourcePolicy maps the standard abilities to permissions:

    Abilities Permissions
    viewAny, view view_x
    create, update edit_x
    delete delete_x
    restore, viewTrash delete_x or force_delete_x
    forceDelete force_delete_x

    Other abilities come from $abilities (a list of permissions means the user needs any of them); unknown abilities are denied. Optional properties:

    • $owned = true makes update and delete need edit_others_x / delete_others_x, or edit_x / delete_x plus $model->isOwnedBy($user).
    • $guard = 'web_admin' checks that guard's permissions.

    Policy methods type-hint Illuminate\Database\Eloquent\Model $user, so any user model works.

    In the app

    php artisan permissions:sync            # create/update permissions and roles (idempotent)
    php artisan permissions:sync --prune    # also delete permissions removed from a registered group

    The sync is also available as Javaabu\Permissions\Database\Seeders\PermissionsSeeder, or Permissions::sync().

    config/permissions.php (publish with --tag=permissions-config):

    • guards: the guards permissions are created for. When empty, the app's default guard is used.
    • roles: by default, super_admin with '*', which keeps it synced to every permission. Pattern lists such as ['view_*'] only ever add permissions.
    • policies: [Export::class => AppExportPolicy::class] overrides a package policy.
    • default_role, log_activity.

    Policy precedence is: config('permissions.policies'), then the app's own Gate::policy() (in any provider), then the package default. Package policies are applied after the app has booted, so the app always wins.

    More of the API

    • ->guards(['web_admin']) limits a group to those guards.
    • ->grantTo('editor', only: [...]) gives an existing role a group's permissions on sync.
    • ->label('Exports') sets the group's label.
    • Permissions::role('auditor', 'Auditor', ['view_*'], guards: 'web_admin') defines a role in code.
    • Multi-auth helpers:
      • Permissions::guardFor($user)
      • Permissions::check(['view_exports'], 'web_admin')
      • $user->anyPermission([...], 'web_admin')
      • @anypermission([...], 'web_admin')
      • Role::forGuard(), Permission::forGuard() and Permission::inGroup()
    • Descriptions and labels are English strings used as translation keys. Ship their Dhivehi in your package's lang/dv.json (loadJsonTranslationsFrom). They're shown through $permission->translated_description and $permission->group_label.

    Breaking changes from v2 that may affect you

    • No hard dependency on javaabu/helpers or javaabu/activitylog. Activity logging is optional, through spatie/laravel-activitylog.
    • Role no longer implements AdminModel.
    • Factories are now Javaabu\Permissions\Database\Factories\*.

    Docs (docs/, llms.txt, Boost skill) are next. Controllers and routes for roles (#11) follow.

  4. n4ff4h commented on Nov 4, 2026

    @n4ff4h
    CollaboratorAuthor

    Status: everything unblocked is done; views are blocked

    On next-gen (CI green on the full matrix):

    Blocked:

    Needs the user:

    The next-gen → main PR will be opened when the epic is done.

  5. n4ff4h commented on Nov 4, 2026

    @n4ff4h
    CollaboratorAuthor

    Final summary

    v3 is finished on next-gen, and CI is green on every job: PHP 8.4/8.5 × Laravel 12/13 × lowest/stable, Pint, React pages, and without translatable. The PR into main is #15, left for the user to review and merge.

    Since the last update

    • Blade (javaabu/forms) and Inertia React (@javaabu/forms) views #10 role pages: in Blade (javaabu/forms v2) and Inertia React (@javaabu/forms, shipped through javaabu/manifest), with the same props in both. They have search, a card per permission group with select all and none, delete confirmation, and right-to-left layout. Apps can reuse RoleForm, PermissionGroupFields and RoleActions from @javaabu/permissions.
    • Optional javaabu/translatable support #12 translatable descriptions: role descriptions can be translated through Models\Translatable\Role and Support\Translations. A new migration adds nullable translations and lang columns to the roles table. Forms send one field per language (description_en, description_dv), and it's tested with and without translatable.
    • Documentation for people and agents #7 and Dhivehi localization and RTL #9: the view docs and translatable docs are written; RTL and Thaana are handled in the views; 22 more Dhivehi strings, with the review table updated. llms.txt, Boost and the changelog are updated.
    • Tests: 105 PHP and 7 Vitest.

    For other packages

    Still open, needs a person

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    epicTracks a whole packagenext-genNext-gen Javaabu packages

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions