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:
- 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);
}
}
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).
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]);
}
}
}
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?