PHPackages                             malinichevvv/yii2-access - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. malinichevvv/yii2-access

ActiveYii2-extension[Authentication &amp; Authorization](/categories/authentication)

malinichevvv/yii2-access
========================

Powerful, flexible RBAC extension for Yii2 with role inheritance, dynamic rules, PHP 8 attributes, caching and audit log

1.0.0(3w ago)30MITPHPPHP &gt;=8.1

Since May 18Pushed 3w agoCompare

[ Source](https://github.com/malinichevvv/yii2-access)[ Packagist](https://packagist.org/packages/malinichevvv/yii2-access)[ RSS](/packages/malinichevvv-yii2-access/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (2)Versions (2)Used By (0)

yii2-access
===========

[](#yii2-access)

A powerful, flexible RBAC extension for **Yii2** with:

- **Hierarchical role inheritance** — recursive CTE for MySQL 8+ / PostgreSQL, automatic fallback for older MySQL
- **Permission groups** — organise permissions by module for clean UI trees
- **Dynamic rules** — attach a callable to any permission for contextual access decisions
- **Two-layer caching** — per-request in-memory pool + configurable persistent cache (Redis, etc.) with tag-based invalidation
- **PHP 8 attributes** — `#[RequirePermission]` / `#[RequireRole]` for declarative, zero-boilerplate controller guards
- **Behaviors** — `AccessControlBehavior` (controller) and `UserAccessBehavior` (User model)
- **Config-based filter** — `PermissionFilter` for teams who prefer rules in `behaviors()`
- **Static facade** — `Am::checkAccess()` for concise one-liner checks anywhere
- **Optional audit log** — append-only table for every access-changing operation
- **Multi-tenant** — optional `company_id` column scopes roles per tenant
- **Full AR models** — for all tables, ready for use in admin UIs
- **Two migrations** — core tables + audit log (can be applied independently)

---

Requirements
------------

[](#requirements)

RequirementVersionPHP≥ 8.1Yii2~2.0---

Installation
------------

[](#installation)

```
composer require malinichevvv/yii2-access
```

Run the migrations:

```
php yii migrate --migrationPath=@vendor/malinichevvv/yii2-access/src/migrations
```

Or register the path in your console config:

```
// console/config/main.php
'controllerMap' => [
    'migrate' => [
        'class' => 'yii\console\controllers\MigrateController',
        'migrationPath' => [
            '@app/migrations',
            '@vendor/malinichevvv/yii2-access/src/migrations',
        ],
    ],
],
```

---

Configuration
-------------

[](#configuration)

The extension auto-registers the `access` component via bootstrap. You can override it:

```
// common/config/main.php
'components' => [
    'access' => [
        'class'          => \malinichevvv\access\AccessManager::class,
        'db'             => 'db',      // DB component ID
        'cache'          => 'cache',   // Cache component ID (false = disable)
        'enableCache'    => true,
        'enableAuditLog' => true,
        'multiTenant'    => false,     // Set true for company-scoped roles
    ],
],

// Register bootstrap so the component is available everywhere:
'bootstrap' => ['log', 'access'],
```

---

Basic Usage
-----------

[](#basic-usage)

### Via static facade

[](#via-static-facade)

```
use malinichevvv\access\Am;

// Check a single permission
if (Am::checkAccess($userId, 'order.create')) {
    // ...
}

// Check multiple at once
$map = Am::checkMultipleAccess($userId, ['order.create', 'order.delete', 'report.view']);
// ['order.create' => true, 'order.delete' => false, 'report.view' => true]

// Check by role code
if (Am::hasRole($userId, 'admin')) { ... }
if (Am::hasRole($userId, ['admin', 'super_admin'])) { ... } // OR logic
```

### Via component

[](#via-component)

```
/** @var \malinichevvv\access\AccessManager $access */
$access = Yii::$app->access;
$access->checkAccess($userId, 'order.create');
```

---

PHP 8 Attributes (Recommended)
------------------------------

[](#php-8-attributes-recommended)

Attach `AccessControlBehavior` to your controller, then annotate actions:

```
use malinichevvv\access\attributes\RequirePermission;
use malinichevvv\access\attributes\RequireRole;
use malinichevvv\access\behaviors\AccessControlBehavior;

class OrderController extends \yii\web\Controller
{
    public function behaviors(): array
    {
        return [
            'access' => AccessControlBehavior::class,
        ];
    }

    #[RequirePermission('order.view')]
    public function actionIndex(): string { ... }

    #[RequirePermission('order.create')]
    public function actionCreate(): string { ... }

    // Both permissions must be held
    #[RequirePermission('order.view')]
    #[RequirePermission('report.generate')]
    public function actionReport(): string { ... }

    // Role check — at least one role in the array must match
    #[RequireRole(['admin', 'super_admin'])]
    public function actionDelete(): string { ... }

    // Role AND permission
    #[RequireRole('manager')]
    #[RequirePermission('order.approve')]
    public function actionApprove(): string { ... }
}
```

Class-level attributes apply to **all** actions in the controller:

```
#[RequireRole('admin')]
class AdminController extends \yii\web\Controller
{
    public function behaviors(): array
    {
        return ['access' => AccessControlBehavior::class];
    }

    // All actions automatically require 'admin' role
    public function actionIndex(): string { ... }
    public function actionUsers(): string { ... }
}
```

---

Config-based Filter
-------------------

[](#config-based-filter)

For teams that prefer Yii2's declarative style:

```
use malinichevvv\access\filters\PermissionFilter;

public function behaviors(): array
{
    return [
        'permission' => [
            'class' => PermissionFilter::class,
            'rules' => [
                ['allow' => true,  'actions' => ['index', 'view']],
                ['allow' => true,  'actions' => ['create'], 'permissions' => ['order.create']],
                ['allow' => true,  'actions' => ['delete'], 'roles'       => ['admin']],
                ['allow' => true,  'actions' => ['export'], 'roles' => ['manager'], 'permissions' => ['report.export']],
                ['allow' => false], // deny all others
            ],
        ],
    ];
}
```

---

User Model Behavior
-------------------

[](#user-model-behavior)

Add `UserAccessBehavior` to your `User` ActiveRecord to call access checks directly on the model:

```
// In your User model:
public function behaviors(): array
{
    return [
        'access' => \malinichevvv\access\behaviors\UserAccessBehavior::class,
    ];
}

// Usage:
$user = User::findOne($id);

$user->can('order.create');                     // bool
$user->hasRole('admin');                        // bool
$user->hasRole(['admin', 'super_admin']);        // bool — OR
$user->canAll(['order.create', 'order.view']);  // ['order.create' => true, ...]

$user->getPermissions();                        // string[]
$user->getPermissionsDetailed();                // grouped with inheritance info
$user->getPermissionsForUI();                   // UI-ready tree
$user->getPermissionsWithSources();             // direct/inherited split

$user->getRoles();                              // role records (with inheritance)
$user->getRoles(false);                         // direct roles only
$user->getEffectiveRoleIds();                   // int[]
$user->getDirectRoleIds();                      // int[]

$user->assignRole($roleId);                     // void
$user->revokeRole($roleId);                     // void
```

---

Managing Roles &amp; Permissions
--------------------------------

[](#managing-roles--permissions)

```
use malinichevvv\access\Am;

// Create a permission group
$groupId = Am::createPermissionGroup('Orders', 'crm', 'All order-related permissions');

// Create permissions
$createId = Am::createPermission('order.create', 'Create a new order', $groupId);
$deleteId = Am::createPermission('order.delete', 'Delete an order',   $groupId);

// Create a role
$roleId = Am::createRole('Manager', 'manager', 'Manages orders and clients');

// Assign permissions to the role
Am::addPermissionToRole($roleId, $createId);
Am::addPermissionToRole($roleId, $deleteId);

// Assign role to a user
Am::assignRole($userId, $roleId);

// Revoke role
Am::revokeRole($userId, $roleId);

// Update / delete
Am::updateRole($roleId, ['name' => 'Senior Manager']);
Am::deleteRole($roleId); // fails for system roles
```

---

Role Inheritance
----------------

[](#role-inheritance)

Permissions flow **upward**: a child role inherits all permissions of its parent roles.

```
admin
 └── manager        (manager inherits admin's permissions)
      └── operator  (operator inherits manager's + admin's permissions)

```

```
// admin → manager (manager inherits admin)
Am::addRoleInheritance($adminRoleId, $managerRoleId);

// manager → operator
Am::addRoleInheritance($managerRoleId, $operatorRoleId);

// Cycle detection — throws Exception
Am::canInherit($adminRoleId, $adminRoleId); // false (self)
Am::canInherit($operatorRoleId, $adminRoleId); // false (would create cycle)

// Query hierarchy
Am::getParentRoles($managerRoleId);            // direct parents
Am::getParentRoles($operatorRoleId, true);     // all ancestors recursively
Am::getChildRoles($adminRoleId, true);         // all descendants recursively
Am::getRolePermissionsWithInheritance($roleId); // permissions with direct/inherited flags
```

---

Dynamic Rules
-------------

[](#dynamic-rules)

Attach a callable to any permission for contextual access decisions evaluated **after** the static permission check:

```
namespace App\Access\Rules;

class OwnerRule
{
    public static function check(int $userId, ?array $params): bool
    {
        $orderId = $params['order_id'] ?? null;
        if (!$orderId) {
            return false;
        }
        // Only the order's creator may delete it
        return Order::find()->where(['id' => $orderId, 'created_by' => $userId])->exists();
    }
}
```

```
// Register the rule
Am::addDynamicRule($permissionId, 'App\Access\Rules\OwnerRule::check', 'Owner check for orders');

// Check with params — static check + dynamic rule both evaluated
Am::checkAccess($userId, 'order.delete', ['order_id' => $orderId]);

// Remove the rule
Am::removeDynamicRule($permissionId);
```

---

Multi-Tenant Mode
-----------------

[](#multi-tenant-mode)

Enable `multiTenant = true` to scope roles per company:

```
'access' => [
    'class'       => \malinichevvv\access\AccessManager::class,
    'multiTenant' => true,
],
```

```
// Roles are scoped to company_id
$roleId = Am::createRole('Manager', 'manager', 'Company manager', false, false, $companyId);

// Query company roles
$roles = Am::getRolesByCompany($companyId);
$role  = Am::getRoleByCompanyWithCode($companyId, 'manager');
```

---

Analytics &amp; Comparison
--------------------------

[](#analytics--comparison)

```
// Compare permissions between two users
$diff = Am::compareUsersAccess($userId1, $userId2);
// [
//   'only_user1'         => ['order.delete', ...],
//   'only_user2'         => ['report.export', ...],
//   'common'             => ['order.view', 'order.create'],
//   'similarity_percent' => 66.7,
// ]
```

---

Console Commands
----------------

[](#console-commands)

Register the controller in your console config once:

```
// console/config/main.php
'controllerMap' => [
    'access' => \malinichevvv\access\console\AccessController::class,
],
```

CommandDescription`php yii access/flush-cache`Invalidate all `access:permissions` and `access:roles` cache tags`php yii access/list-roles`List all roles with system/default flags`php yii access/list-roles --company-id=5`Roles for a specific company (multi-tenant)`php yii access/list-permissions`List all permissions grouped by module`php yii access/list-permissions --module=crm`Permissions for a specific module`php yii access/check 42 order.create`Check if user #42 has `order.create``php yii access/user-roles 42`Show direct and inherited roles for user #42`php yii access/user-permissions 42`Show all permissions for user #42 with sources---

Configurable Cache TTLs
-----------------------

[](#configurable-cache-ttls)

Cache durations are **instance properties**, not hardcoded constants, so you can tune them per environment:

```
'components' => [
    'access' => [
        'class'                => \malinichevvv\access\AccessManager::class,
        // Defaults shown — override as needed:
        'cacheDurationShort'    => 1800,   // 30 min — user role assignments
        'cacheDurationMedium'   => 3600,   // 1 h   — user permission sets, role details
        'cacheDurationLong'     => 7200,   // 2 h   — role hierarchy, role→permission links
        'cacheDurationVeryLong' => 86400,  // 24 h  — dynamic rule metadata, module groups
    ],
],
```

The constants (`AccessManager::CACHE_DURATION_*`) remain available as documented defaults and can be referenced in your own code.

---

Events
------

[](#events)

The `AccessManager` component extends `yii\base\Component` and fires events at every key point. Attach listeners anywhere in your application bootstrap or module `init()`.

### Access check events

[](#access-check-events)

ConstantClassWhen`EVENT_BEFORE_CHECK_ACCESS``AccessCheckEvent`Before any permission check; **can short-circuit**`EVENT_AFTER_CHECK_ACCESS``AccessCheckEvent`After every check; carries `$result``EVENT_ACCESS_DENIED``AccessCheckEvent`Only when the check **fails****Super-admin bypass** (short-circuit pattern):

```
Yii::$app->access->on(
    AccessManager::EVENT_BEFORE_CHECK_ACCESS,
    function (AccessCheckEvent $event) {
        if (isSuperAdmin($event->userId)) {
            $event->isHandled = true;  // skip DB/cache entirely
            $event->result    = true;
        }
    }
);
```

**Access denied logging:**

```
Yii::$app->access->on(
    AccessManager::EVENT_ACCESS_DENIED,
    function (AccessCheckEvent $event) {
        Yii::warning(
            "Denied: user={$event->userId} perm={$event->permissionCode}",
            'access'
        );
    }
);
```

### Role events

[](#role-events)

ConstantCancellableProperties on event`EVENT_BEFORE_ASSIGN_ROLE`yes`userId`, `roleId``EVENT_AFTER_ASSIGN_ROLE`—`userId`, `roleId``EVENT_BEFORE_REVOKE_ROLE`yes`userId`, `roleId``EVENT_AFTER_REVOKE_ROLE`—`userId`, `roleId``EVENT_BEFORE_CREATE_ROLE`yes`data``EVENT_AFTER_CREATE_ROLE`—`roleId`, `createdRoleId`, `data``EVENT_BEFORE_UPDATE_ROLE`yes`roleId`, `data``EVENT_AFTER_UPDATE_ROLE`—`roleId`, `data``EVENT_BEFORE_DELETE_ROLE`yes`roleId``EVENT_AFTER_DELETE_ROLE`—`roleId``EVENT_BEFORE_ADD_ROLE_INHERITANCE`yes`parentRoleId`, `childRoleId``EVENT_AFTER_ADD_ROLE_INHERITANCE`—`parentRoleId`, `childRoleId``EVENT_BEFORE_REMOVE_ROLE_INHERITANCE`yes`parentRoleId`, `childRoleId``EVENT_AFTER_REMOVE_ROLE_INHERITANCE`—`parentRoleId`, `childRoleId`**Cancellable example** — guard system roles from deletion:

```
Yii::$app->access->on(
    AccessManager::EVENT_BEFORE_DELETE_ROLE,
    function (RoleEvent $event) {
        if ($event->roleId === MY_PROTECTED_ROLE_ID) {
            $event->isValid = false; // vetoes the delete
        }
    }
);
```

**After-event example** — notify a user when their role changes:

```
Yii::$app->access->on(
    AccessManager::EVENT_AFTER_ASSIGN_ROLE,
    function (RoleEvent $event) {
        Notification::send($event->userId, 'Your access permissions have been updated.');
    }
);
```

### Permission events

[](#permission-events)

ConstantCancellableProperties on event`EVENT_BEFORE_CREATE_PERMISSION`yes`permissionCode`, `data``EVENT_AFTER_CREATE_PERMISSION`—`permissionId`, `createdPermissionId`, `permissionCode`, `data``EVENT_BEFORE_UPDATE_PERMISSION`yes`permissionId`, `data``EVENT_AFTER_UPDATE_PERMISSION`—`permissionId`, `data``EVENT_BEFORE_DELETE_PERMISSION`yes`permissionId``EVENT_AFTER_DELETE_PERMISSION`—`permissionId``EVENT_BEFORE_ADD_PERMISSION_TO_ROLE`yes`roleId`, `permissionId``EVENT_AFTER_ADD_PERMISSION_TO_ROLE`—`roleId`, `permissionId``EVENT_BEFORE_REMOVE_PERMISSION_FROM_ROLE`yes`roleId`, `permissionId``EVENT_AFTER_REMOVE_PERMISSION_FROM_ROLE`—`roleId`, `permissionId`**Auto-grant to super-admin on permission creation:**

```
Yii::$app->access->on(
    AccessManager::EVENT_AFTER_CREATE_PERMISSION,
    function (PermissionEvent $event) {
        Am::addPermissionToRole(SUPERADMIN_ROLE_ID, $event->createdPermissionId);
    }
);
```

**Block sensitive permission assignment:**

```
Yii::$app->access->on(
    AccessManager::EVENT_BEFORE_ADD_PERMISSION_TO_ROLE,
    function (PermissionEvent $event) {
        $sensitive = ['payment.refund', 'user.delete', 'role.manage'];
        if (in_array($event->permissionCode, $sensitive, true)) {
            $event->isValid = false;
            Yii::warning("Blocked assignment of sensitive permission to role #{$event->roleId}");
        }
    }
);
```

### Detaching listeners

[](#detaching-listeners)

```
$handler = function (RoleEvent $event) { ... };
Yii::$app->access->on(AccessManager::EVENT_AFTER_ASSIGN_ROLE, $handler);

// Later:
Yii::$app->access->off(AccessManager::EVENT_AFTER_ASSIGN_ROLE, $handler);
```

---

Database Schema
---------------

[](#database-schema)

TableDescription`access_permission_groups`Module-based groups for organising permissions`access_permissions`Individual permission codes`access_roles`Role definitions (optionally company-scoped)`access_role_permissions`Role ↔ permission pivot`access_role_includes`Role inheritance (parent → child)`access_user_roles`User ↔ role pivot`access_dynamic_rules`Callable-based contextual rules per permission`access_audit_log`Append-only audit trail (separate migration)---

Caching
-------

[](#caching)

The component uses a two-layer cache strategy:

1. **Per-request in-memory pool** — eliminates repeated DB/cache round-trips within a single HTTP request
2. **Persistent cache** (Redis, Memcache, etc.) — shared across requests with tag-based invalidation

All mutating operations automatically invalidate the relevant cache tags. You can also clear the request pool manually (e.g. in tests):

```
Am::clearRequestCache();
// or
Yii::$app->access->clearRequestCache();
```

To disable caching entirely:

```
'access' => ['class' => AccessManager::class, 'enableCache' => false],
```

---

Audit Log
---------

[](#audit-log)

When `enableAuditLog = true` (default), every access-changing operation writes a row to `access_audit_log`. The table has no foreign keys to `users` so the log survives user deletion.

To disable:

```
'access' => ['class' => AccessManager::class, 'enableAuditLog' => false],
```

---

i18n
----

[](#i18n)

Error messages are translatable. Add to your `i18n` config:

```
'i18n' => [
    'translations' => [
        'access' => [
            'class'          => 'yii\i18n\PhpMessageSource',
            'basePath'       => '@vendor/malinichevvv/yii2-access/src/messages',
            'sourceLanguage' => 'en',
        ],
    ],
],
```

Supported languages: `en`, `ru`, `uk`. Contributions for other languages are welcome.

---

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance95

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

22d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/9015180ef2bae15038be2f691555cc97a7d8b01f78fa55b3413b0151d1caaff5?d=identicon)[malinichevvv](/maintainers/malinichevvv)

---

Tags

accessrolespermissionsrbacyii2access-control

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/malinichevvv-yii2-access/health.svg)

```
[![Health](https://phpackages.com/badges/malinichevvv-yii2-access/health.svg)](https://phpackages.com/packages/malinichevvv-yii2-access)
```

###  Alternatives

[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k3.5M115](/packages/bezhansalleh-filament-shield)[erag/laravel-role-permission

A simple and easy-to-install role and permission management package for Laravel, supporting versions 10.x and 11.x

404.2k](/packages/erag-laravel-role-permission)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
