0

I have a couple of bundles and want to define some roles and hierarchy inside them. The app must somehow merge them into one hierarchy and I want to force somehow use this hierarchy security-bundle.

What I tried to do:

  1. I made CoreBundle class with compiler pass and tagging all classes that have metadata about roles:
class CoreBundle extends AbstractBundle
{
    public function build(ContainerBuilder $container): void
    {
        parent::build($container);

        $container->addCompilerPass(new RoleHierarchyPass());
    }

    public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
    {
        $container->import('../config/services.yaml');
        $builder->registerForAutoconfiguration(RoleHierarchyInterface::class)->addTag(RoleHierarchyInterface::class);
    }
}
  1. RoleHierarchyInterface is simple:
interface RoleHierarchyInterface
{
    public function getHierarchy(): array;
}

And this is an example of this interface implementation:

class CoreRoleHierarchy implements RoleHierarchyInterface
{
    public function getHierarchy(): array
    {
        return [
            'ROLE_DEVELOPER' => ["ROLE_USER"],
            'ROLE_ADMIN' => ["ROLE_USER"]
        ];
    }
}

I'm planning to add more methods to describing roles in future (for forms for example).

  1. RoleHierarchyPass implementation:
class RoleHierarchyPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if ($container->hasDefinition(RoleHierarchyAggregator::class)) {
            $taggedServices = $container->findTaggedServiceIds(RoleHierarchyInterface::class);
            $serviceReferences = [];
            foreach ($taggedServices as $serviceId => $tags) {
                $container->getDefinition($serviceId);
                $serviceReferences[] = new Reference($serviceId);
            }
            $definition = $container->findDefinition(RoleHierarchyAggregator::class);
            $definition->setArguments(['$roleHierarchyServices' => $serviceReferences]);
        }
    }
}
  1. RoleHierarchyAggregator implementation:
readonly class RoleHierarchyAggregator
{
    public function __construct(private iterable $roleHierarchyServices = [])
    {
    }

    public function getMergedHierarchy(): array
    {
        $mergedHierarchy = [];
        foreach ($this->roleHierarchyServices as $roleHierarchyService) {
            if ($roleHierarchyService instanceof RoleHierarchyInterface) {
                $mergedHierarchy = array_merge_recursive($mergedHierarchy, $roleHierarchyService->getHierarchy());
            }
        }
        return $mergedHierarchy;
    }
}

I wanted to change security.yaml role_hierarchy but I can't do it in the RoleHierarchyAggregator service. I tried to do it on boot method of bundle:

public function boot()
{
    $container = $this->container;
    $roleHierarchyAggregator = $container->get(RoleHierarchyAggregator::class);
    dd($roleHierarchyAggregator);
}

But it throws the following exception:

The "FooBar\CoreBundle\Security\RoleHierarchyAggregator" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.

Tried to add CompilerPass to Kernel:

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    protected function build(ContainerBuilder $container): void
    {
        parent::build($container);

        $container->addCompilerPass(new MergeRoleHierarchyPass());
    }
}
class MergeRoleHierarchyPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $taggedServices = $container->findTaggedServiceIds(RoleHierarchyInterface::class);
        foreach ($taggedServices as $serviceId => $tags) {
            dd($container->get($serviceId));
        }
        dd($container->getParameter('security.role_hierarchy.roles'));
    }
}

But that throws an exception too:

Constructing service "FooBar\CoreBundle\Security\CoreRoleHierarchy" from a parent definition is not supported at build time.

I don't know what to do. Is it possible to retrieve roles from different bundles, merge them and then pass to security.yaml? Or are there any other alternatives?

1 Answer 1

0

It is possible to take a look at the PreprendExtension to configure the security bundle configuration from other bundles. For example, your CoreBundle can set its role for the security bundle:

$container->prependExtensionConfig('security', [
    'role_hierarchy' => [
        'ROLE_DEVELOPER' => ["ROLE_USER"],
        'ROLE_ADMIN'     => ["ROLE_USER"]
    ],
]);

ref : https://symfony.com/doc/current/bundles/prepend_extension.html

1
  • That not will work because config will be registered only from one bundle. As your ref said: If there is more than one bundle that prepends the same extension and defines the same key, the bundle that is registered first will take priority: next bundles won't override this specific config setting. In my case there are multiple bundles with their own roles and hierarchies so I have to merge them somehow.
    – WGPavell
    Commented Nov 10 at 9:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.