Search by

tobento / acl

TOBENTOch

This package is abandoned and no longer maintained. The author suggests using the tobento/service-acl package instead.

A simple role and user-level access control system.

2.0.1 2026-10-10 09:02 UTC

This package is auto-updated.

Last update: 2026-10-10 09:03:32 UTC


README

The ACL Service is a simple role and user-level access control system.

Table of Contents

Getting started

Add the latest version of the Acl service running this command.

composer require tobento/service-acl

Requirements

  • PHP 8.4 or greater

Highlights

  • Framework-agnostic, will work with any project
  • Customize permission behaviour
  • Decoupled design

Simple Example

Here is a simple example of how to use the Acl service.

use Tobento\Service\Acl\Acl;
use Tobento\Service\Acl\Authorizable;
use Tobento\Service\Acl\AuthorizableAware;
use Tobento\Service\Acl\Role;

// User class example.
class User implements Authorizable
{
    use AuthorizableAware;
    
    public function __construct(
        protected string $name,
    ) {}
}

// Create Acl.
$acl = new Acl();

// Adding rules.
$acl->rule('articles.read')
    ->title('Article Read')
    ->description('If a user can read articles');
    
$acl->rule('articles.create');
$acl->rule('articles.update');

// Create role.
$guestRole = new Role('guest');

// Adding permissions on role.
$guestRole->addPermissions(['articles.read']);

// Create and set user role.
$user = new User('Nick')->setRole($guestRole);

// Adding permissions on user.
// If permissions are set on user, role permissions will not count anymore.
$user->addPermissions(['articles.read']);

// Set current user.
$acl->setCurrentUser($user);

// Adding additional permissions for the current user only.
$acl->addPermissions(['articles.create']);

// Check permissions for current user.
if ($acl->can('articles.read')) {
    // user has permission to read articles.
}

// check permission for specific user.
if ($acl->cant(key: 'articles.read', user: $user)) {
    // user has not permission to read articles.
}

Documentation

Rules

Adding and getting rules.

use Tobento\Service\Acl\Acl;
use Tobento\Service\Acl\RuleInterface;

// Create Acl.
$acl = new Acl();

// Add default rule.
$acl->rule('articles.read');

// Add custom rule.
$acl->addRule(RuleInterface $rule);

// Get rules.
foreach($acl->getRules() as $rule)
{
    $key = $rule->getKey();
    $inputKey = $rule->getInputKey();
    $title = $rule->getTitle();
    $description = $rule->getDescription();
    $area = $rule->getArea();
}

// get specific rules
$rule = $acl->getRule('articles.read');

Default Rule

The default rule has the following permission behaviour:

use Tobento\Service\Acl\Acl;

// Create Acl.
$acl = new Acl();

$acl->rule('articles.read');
$acl->rule('articles.update');

// Create role.
$role = new Role('guest');

// Create and set user role.
$user = new User('Nick')->setRole($role);

// Adding permissions on acl, only for current user.
$acl->addPermissions(['articles.read']);

// Adding permissions on role.
$role->addPermissions(['articles.read']);

// Adding permissions on user.
// If permissions are set on user, role permissions will not count anymore.
// Only acl and user specific permissions.
$user->addPermissions(['articles.read']);

Areas bahviour:

use Tobento\Service\Acl\Acl;

// Create Acl.
$acl = new Acl();

$acl->rule('articles.read', 'frontend');
$acl->rule('articles.update', 'backend');

// Guest Role taking only frontend rules into account,
// ignoring any permission from backend rules even if permission is given.
$role = new Role('guest', ['frontend']);

// Editor can have frontend and backend rules.
$role = new Role('editor', ['frontend', 'backend']);

Default Rule Custom

You can easily add a custom handler for extending a specific rule behaviour.

use Tobento\Service\Acl\Acl;
use Tobento\Service\Acl\Authorizable;
use Tobento\Service\Acl\AuthorizableAware;
use Tobento\Service\Acl\Role;

// User class example.
class User implements Authorizable
{
    use AuthorizableAware;
    
    public function __construct(
        protected string $name,
    ) {}
}

// Article class example
class Article
{    
    public function __construct(
        protected string $name,
        protected array $roles = [],
        protected null|Authorizable $user = null
    ) {}

    public function getName(): string
    {
        return $this->name;
    }
    
    public function getUser(): null|Authorizable
    {
        return $this->user;
    }

    public function getRoles(): array
    {
        return $this->roles;
    }    
}

// Create Acl.
$acl = new Acl();

// Rule to check if user is allowed to access a specific resource.
$acl->rule('resource')
    ->needsPermission(false)
    ->handler(function(Authorizable $user, null|Authorizable $resourceUser): bool {

        if (is_null($resourceUser)) {
            return false;
        }

        return $user === $resourceUser;
    });

// Rule to check if user has role for a specific resource.
$acl->rule('has_role')
    ->needsPermission(false)
    ->handler(function(Authorizable $user, array $roles = []) {

        if (empty($roles)) {
            return true;
        }

        return in_array($user->role()->key(), $roles);                    
    });
            
$user = (new User('Nick'))->setRole(new Role('editor'));

$acl->setCurrentUser($user);    

$article = new Article('About us', ['editor'], $user);

// Check resource access.
if ($acl->can('resource', [$article->getUser()])) {
    // user can access about page.
}

// Check resource role access.
if ($acl->can('has_role', [$article->getRoles()])) {
    // user has the right role to access this resource.
}

Custom Rules

You can easily add a custom rule for a different permission strategy.

Your Rule must implement the following RuleInterface.

/**
 * RuleInterface
 */
interface RuleInterface
{
    /**
     * Get the key.
     *
     * @return string The key such as 'user.create'.
     */    
    public function getKey(): string;

    /**
     * Get the input key. May be used for form input.
     *
     * @return string The key such as 'user_create'.
     */    
    public function getInputKey(): string;    
    
    /**
     * Get the title.
     *
     * @return string The title
     */    
    public function getTitle(): string;
    
    /**
     * Get the description.
     *
     * @return string The description
     */    
    public function getDescription(): string;
    
    /**
     * Get the area.
     *
     * @return string
     */    
    public function getArea(): string;

    /**
     * If the rule requires permissions to match the rule.
     *
     * @return bool
     */    
    public function requiresPermission(): bool;    

    /**
     * Return if the rule matches the criteria.
     *
     * @param AclInterface
     * @param string A permission key 'user.create'.
     * @param array Any parameters for custom handler
     * @param null|Authorizable
     * @return bool True if rule matches, otherwise false
     */    
    public function matches(
        AclInterface $acl,
        string $key,
        array $parameters = [],
        ?Authorizable $user = null
    ): bool;    
}

Lets make a custum rule for just letting user specific permissions have access ignoring acl and role permissions.

use Tobento\Service\Acl\Acl;
use Tobento\Service\Acl\AclInterface;
use Tobento\Service\Acl\RuleInterface;
use Tobento\Service\Acl\Authorizable;
use Tobento\Service\Acl\AuthorizableAware;
use Tobento\Service\Acl\Role;

// Custom rule
class CustomRule implements RuleInterface
{    
    public function __construct(
        protected string $key,
        protected string $area,
    ) {}
 
    public function getKey(): string
    {
        return $this->key;
    }

    public function getInputKey(): string
    {
        return $this->key;
    }
       
    public function getTitle(): string
    {        
        return $this->key;
    }
   
    public function getDescription(): string
    {
        return '';
    }
 
    public function getArea(): string
    {
        return $this->area;
    }
   
    public function requiresPermission(): bool
    {
        return true;
    }
 
    public function matches(
        AclInterface $acl,
        string $key,
        array $parameters = [],
        ?Authorizable $user = null
    ): bool {
            
        $user = $user ?: $acl->getCurrentUser();

        // not user at all
        if (is_null($user)) {
            return false;
        }
        
        // user needs a role.
        if (! $user->hasRole()) {
            return false;
        }

        // collect only user permissions.
        if (!$user->hasPermissions()) {
            return false;
        }
        
        $permissions = $user->getPermissions();

        // permission check
        if (!in_array($key, $permissions)) {
            return false;
        }
        
        // area check
        if (!in_array($this->getArea(), $user->role()->areas())) {
            return false;
        }
        
        return true;
    }
}

// User class example.
class User implements Authorizable
{
    use AuthorizableAware;
    
    public function __construct(
        protected string $name,
    ) {}
}

// Create Acl.
$acl = new Acl();

// Adding default rules.
$acl->addRule(new CustomRule('articles.read', 'frontend'));
$acl->addRule(new CustomRule('articles.create', 'frontend'));

// Create role.
$role = new Role('guest');

// Adding permissions on role does has no effect.
$role->addPermissions(['articles.read']);

// Create and set user role.
$user = new User('Nick')->setRole($role);
$user->addPermissions(['articles.create']);

// Set current user.
$acl->setCurrentUser($user);

if ($acl->can('articles.create')) {
    // user has permission to read articles.
}

Permissions

The following methods are available on objects implementing the Permissionable Interface or the Authorizable Interface.

  • Tobento\Service\Acl\Acl::class
  • Tobento\Service\Acl\Role::class
use Tobento\Service\Acl\Acl;
use Tobento\Service\Acl\Permissionable;
use Tobento\Service\Acl\Authorizable;

// Create Acl.
$acl = new Acl();

// Set all permissions.
$acl->setPermissions(['user.create', 'user.update']);

// Adding more permissions.
$acl->addPermissions(['user.delete']);

$permissions = $acl->getPermissions(); // ['user.create', 'user.update', 'user.delete']

// Has any permissions at all.
$hasPermissions = $acl->hasPermissions();

// Removing permissions.
$acl->removePermissions(['user.delete']);

// Has specific permission.
$hasPermission = $acl->hasPermission('user.update');

Available methods for checking permissions on acl:

use Tobento\Service\Acl\Acl;

// Create Acl.
$acl = new Acl();

// Check permissions for current user.
if ($acl->can('articles.read')) {
    // user has permission to read articles.
}

// Check permission for specific user.
if ($acl->cant(key: 'articles.read', user: $user)) {
    // user has not permission to read articles.
}

// You can check multiple permissions too.
if ($acl->can('articles.create|articles.update')) {
    // user has permission to create and update articles.
}

// Multiple permissions with parameters.
if ($acl->can('articles.create|resource', ['resource' => [$article->getUser()]])) {
    // user has permission to create and access the specific article.
}

Checking permissions on Authorizable object:

For more information on Helper Function visit tobento/helper-function

use Tobento\Service\HelperFunction\Functions;
use Psr\Container\ContainerInterface;
use Tobento\Service\Di\Container;
use Tobento\Service\Acl\Acl;
use Tobento\Service\Acl\AclInterface;
use Tobento\Service\Acl\Authorizable;
use Tobento\Service\Acl\AuthorizableAware;
use Tobento\Service\Acl\Role;

// create container.
$container = new Container();

// Set up Helper Function acl() for supporting
// checking permission directly on Authorizable objects.
$functions = new Functions();
$functions->set(ContainerInterface::class, $container);

// Register Acl functions.
$functions->register('dir/to/acl/functions.php');

// User class example.
class User implements Authorizable
{
    use AuthorizableAware;
    
    public function __construct(
        protected string $name,
    ) {}
}

// Create Acl.
$acl = new Acl();

// Add Acl to container.
$container->set(AclInterface::class, $acl);

// Adding rules.
$acl->rule('articles.read');

// Create role.
$guestRole = new Role('guest');

// Adding permissions on role.
$guestRole->addPermissions(['articles.read']);

// Create and set user role.
$user = new User('Nick')->setRole($guestRole);

// Check permissions on user.
if ($user->can('articles.read')) {
    // user has permission to read articles.
}

// check permission for specific user.
if ($user->cant('articles.read')) {
    // user has not permission to read articles.
}

Roles

Working with roles.

use Tobento\Service\Acl\Acl;
use Tobento\Service\Acl\Role;
use Tobento\Service\Acl\RoleInterface;
use Tobento\Service\Acl\Roles;
use Tobento\Service\Acl\RolesInterface;

// Create Acl.
$acl = new Acl();

// Set roles on acl for later reusage if needed.
$acl->setRoles([
    new Role('guest'),
    new Role('editor'),
]);
// or
$acl->setRoles(new Roles(
    new Role('guest'),
    new Role('editor'),
));

// Get roles:
$roles = $acl->roles();

var_dump($roles instanceof RolesInterface);
// bool(true)

// Iterate roles:
foreach($acl->roles() as $role) {
    $key = $role->key();
    $active = $role->active();
    $areas = $role->areas();
    $name = $role->name();
}

// Get Specific role:
$role = $roles->get('editor');
$role = $acl->getRole('editor'); // or
// null|RoleInterface

// Check if role exists:
var_dump($roles->has('editor'));
var_dump($acl->hasRole('editor')); // or
// bool(true)

// Sort roles returning a new instance:
$roles = $roles->sort(fn(RoleInterface $a, RoleInterface $b): int => $a->name() <=> $b->name());

// Filter roles returning a new instance:
$roles = $roles->filter(fn(RoleInterface $role): bool => $role->active());

// Filter by area roles returning a new instance:
$roles = $roles->area('frontend');

// Filter (in)active roles returning a new instance:
$roles = $roles->active();
$roles = $roles->active(false);

// Returns a new instance only the roles specified:
$roles = $roles->only(['editor']);

// Returns a new instance except the roles specified:
$roles = $roles->except(['editor']);

// Add a role returning a new instance:
$roles = $roles->add(new Role('admin'));

// Remove a role returning a new instance:
$roles = $roles->remove('editor');

// Get first role:
$role = $roles->first();
// null|RoleInterface

// Get column of roles:
$roleNames = $roles->column('name');
$roleNamesByKey = $roles->column('name', 'key');

// Get all roles:
$roles = $roles->all();
$roles = $acl->getRoles(); // or
// array<string, RoleInterface>

Trust Scope

Use the AclTrustScope to temporarily run code with a trusted user and a set of permissions, for example for seeding, tests or background jobs which are not triggered by a user request. When the scope ends, the previous permissions and current user are restored.

use Tobento\Service\Acl\Acl;
use Tobento\Service\Acl\AclTrustScope;
use Tobento\Service\Acl\Role;

// Create Acl.
$acl = new Acl();

$acl->rule('articles.read');
$acl->rule('articles.update');

// Create the trusted user.
$trustedUser = new User('system')->setRole(new Role('system'));

// Create the scope.
$scope = new AclTrustScope($acl)
    ->withUser($trustedUser);

$scope->start();

var_dump($acl->can('articles.update'));
// bool(true)

$scope->end();

// The previous permissions and current user are restored:
var_dump($acl->can('articles.update'));
// bool(false)

Make sure the scope is always ended, even if the trusted code throws:

$scope->start();

try {
    // run trusted code ...
} finally {
    $scope->end();
}

Configuring the scope

Each method returns a new instance, leaving the original unchanged. Configure the scope before calling start().

Method Description
withUser($user) The trusted user which becomes the current user while the scope is active. If not set, the current user is kept.
withPermissions($permissions) The permissions to grant. If not set, every rule key known at start() is granted (full trust).
withConfigure($configurator) A callback receiving the Acl, run on start() to configure it, for example to add rules. May be called multiple times, the callbacks run in the order they were added.

Granting specific permissions only

$scope = new AclTrustScope($acl)
    ->withUser($trustedUser)
    ->withPermissions(['articles.read']);

$scope->start();

var_dump($acl->can('articles.read'));
// bool(true)

var_dump($acl->can('articles.update'));
// bool(false)

$scope->end();

Configuring the Acl

The callbacks run before the permissions are granted, so rules added by them are included when all rule keys are granted.

use Tobento\Service\Acl\AclInterface;
use Tobento\Service\Acl\Authorizable;

$scope = new AclTrustScope($acl)
    ->withUser($trustedUser)
    ->withConfigure(static function (AclInterface $acl): void {
        $acl->rule('active')
            ->description('Rule to check if a resource is active.')
            ->needsPermission(false)
            ->handler(static fn(Authorizable $user, mixed $active): bool => true);
    });

Good to know

  • Permissions are added to the permissions already set on the Acl. end() restores the previous permissions.
  • Rule changes made with withConfigure() are not restored by end(), as the Acl cannot remove or reset rules once added. Only use it for changes you are fine persisting for the life of the Acl instance.
  • Permissions do not bypass areas. The trusted user needs a role which includes the areas of the rules being checked.
  • Calling start() while the scope is active, or end() while it is not active, does nothing. A scope may be started again after it has ended.

Credits