somnambulist/policy-bundle

An implementation of Laravel policies for Symfony Security

Maintainers

Package info

github.com/somnambulist-tech/policy-bundle

Type:symfony-bundle

pkg:composer/somnambulist/policy-bundle

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.1.0 2026-06-14 01:48 UTC

This package is auto-updated.

Last update: 2026-06-14 01:54:19 UTC


README

GitHub Actions Build Status Issues License PHP Version Current Version

Policy bundle provides a version of Laravels security policy implementation for Symfony projects. This includes the Security Voter and support classes for debugging / handling permissions and access checks.

Requirements

  • PHP 8.4+

Installation

Install using composer, or checkout / pull the files from github.com.

  • composer require somnambulist/policy-bundle

Usage

This bundle makes certain presumptions on how you will authorise users in your project. You should have a User object that follows the roles and permissions approach, where permissions can be assigned to roles and/or the User. How you then store and fetch these are entirely up to you.

Provided your User (that implements the standard Symfony UserInterface) can provide access to methods defined in interfaces provided in this bundle, you can take advantage of policy objects and automatic resolution of these objects.

To begin with: you must add to your User object:

  • Somnambulist\Bundles\PolicyBundle\Contracts\CanCheckPermissions
  • Somnambulist\Bundles\PolicyBundle\Contracts\CanCheckRoles

These provide basic methods for checking if a User can perform an action, and if they have a role.

The implementation is left up to you to sort out, and if checking multiple permissions should require all or not to pass checks.

Next: you define your policies. You can place these anywhere in your project, though a dedicated "Policies" folder is recommended. If you are following a modular approach or Domain Driven Design based context, you can add the policies to your infrastructure layer, or however you want.

Regardless you should then add to your services.yaml (or however you define your services), a new resource that will reference the policies (policies are service objects). Policies must implement the PolicyInterface and will be automatically tagged and associated with the service classes in this bundle.

Finally: you can use the standard Symfony access checks as you normally would, including passing objects through. The PolicyVoter will call the appropriate method on your policy class and do the resolution based on how you set it up.

Making Policies

A policy object is a class that implements the Somnamblist\Bundles\PolicyBundle\Contracts\PolicyInterface. The policy then provides the access checks on the subject provided.

Any policy is a service object, so you are not limited to just the passed User / Subject, you can use dependency injection to add repositories and other services to allow for fine-grained access control to resources e.g. by record ownership, date ranges etc.

The most basic form of a policy looks like:

use Somnambulist\Bundles\PolicyBundle\Policies\AbstractPolicy;
use Symfony\Component\Security\Core\User\UserInterface;

class DocumentPolicy extends AbstractPolicy
{
    public const string MANAGE   = 'documents.manage';
    public const string DESTROY  = 'documents.destroy';

    public function supports(): array
    {
        return ['document', 'documents',];
    }

    public function dependencies(): array
    {
        return [];
    }

    public function manage(UserInterface $user, mixed $subject): bool
    {
        return $user->can(self::MANAGE);
    }

    public function destroy(UserInterface $user, mixed $subject): bool
    {
        return $user->can(self::DESTROY);
    }
}

This defines a policy that will operate with "document" objects. Two main permissions are defined to manage (create, view, update, publish, etc), and destroy. For better control "manage" could be split into separate permissions for create, edit, update, view, search, publish, etc. This is left up to your implementation, though class constants are strongly encouraged to prevent typing errors.

The next part is the policy actions; two have been defined that correspond to the manage and destroy permissions. You can have any number of actions defined on your policy to offer fine-grained programmatic control of access to the subject. These methods will be called by the voter and several forms are supported:

  • the camel name e.g. can_create_document -> canCreateDocument()
  • the method name as-is e.g. manage -> manage()

Policy Dependencies

Sometimes certain actions are dependent on having another permission; policies allow defining a required permission that is needed to use another permission. This is intended to be used in an admin panel to ensure that a given permission makes sense to be granted. For example: continuing the document example, documents can have files. To be able to manage files for a document, you should have the document manage role.

Dependencies are defined in an array format, where the key is the permission and the value, the dependency. If all permissions need a dependency, a * can be used. For example:

use Somnambulist\Bundles\PolicyBundle\Policies\AbstractPolicy;
use Symfony\Component\Security\Core\User\UserInterface;

class FilePolicy extends AbstractPolicy
{
    public const string MANAGE   = 'documents.files.manage';
    public const string DESTROY  = 'documents.documents.destroy';

    public function supports(): array
    {
        return ['file', 'files',];
    }

    public function dependencies(): array
    {
        return [
            '*' => DocumentPolicy::MANAGE,

            self::DESTROY => self::MANAGE,
        ];
    }

    public function manage(UserInterface $user, $subject): bool
    {
        return $user->can(self::MANAGE);
    }

    public function destroy(UserInterface $user, $subject): bool
    {
        return $user->can(self::DESTROY);
    }
}

In this example: the file destroy is also dependent on having the file manage permission.

The dependencies can be resolved using the PolicyResolverService::resolveDependenciesForPermissions(). Pass in the permission to resolve permissions for.

Before Policy

Pre-authorization can be done by implementing the BeforePolicy contract. This can be used to e.g. allow full access to a given role or specific user, or to do some other auth check. This should return true to allow, false to deny, and null to defer to the policy.

Syncing Permissions to Roles

Policy bundle includes a console command for synchronising all permissions to a given role. This requires implementing the RoleRepository interface and adding it to your services. This defines 2 methods, the second being the method to sync permissions to a role.

The command will pull all policies and extract the constants (by default, this can be changed by implementing the provides() method on the policy) and then pass them to the role. This is useful to sync any code changes to your DB and ensure that the permissions are pre-assigned to a given role e.g. root or administrator.

Note that the internals of storing permissions are left to your implementation.

You must manually add the commands to your services.yaml as they depend on your RoleRepository implementation.

Tests

PHPUnit 12+ is used for testing. Run tests via vendor/bin/phpunit.

Contributing

Contributions are welcome! Fork the repository and make a PR back. Please ensure that your code is formatted using PSR-12 coding standards, and all PHP files include declare(strict_types=1); on the opening <?php tag. If in doubt about any code-style convention, look at the existing files and follow along.

This library currently targets PHP 8.4.X.

If adding new functionality ensure the README.md file is updated with your changes and include appropriate tests and if possible, language translations with English as the primary requirement.

For bug fixes a failing case must be included in a test. Changes without appropriate tests or that cannot be replicated may be rejected.