WEBcoast Logo

Symfony: Security context in doctrine filters

I one of my recent projects, I wanted to use doctrine filters to dynamically alter queries based on, which role the currently logged-in user had. I wanted to use this to automatically filter inactive entities - based on the `active` property, when the user is not an admin user.

My first approach was to use Symfony's dependency injection, which however, does not work with Doctrine filters. There wasn't a clear solution to this neither in the Symfony nor the Doctrine documentation. So needed to experiment a little. I finally found a solution, using the `loadClassMetadata` event in Doctrine. So I created an event listener:

<?php

namespace MyVendor\MyPackage\EventListener;

use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Events;
use Symfony\Bundle\SecurityBundle\Security;

#[AsDoctrineListener(event: Events::loadClassMetadata)]
class DoctrineFilterSecurityListener
{
    public function __construct(protected Security $security)
    {
    }

    public function __invoke(LoadClassMetadataEventArgs $event): void
    {
        foreach ($event->getEntityManager()->getFilters()->getEnabledFilters() as $filter) {
            if (is_callable([$filter, 'setSecurity'])) {
                $filter->setSecurity($this->security);
            }
        }
    }
}

My filter looked like this:

<?php

namespace MyVendor\MyPackage\Doctrine\Filter;

use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Symfony\Bundle\SecurityBundle\Security;
use MyVendor\MyPackage\Entity\Story;

class StoryActiveFilter extends SQLFilter
{
    protected Security $security;

    public function setSecurity(Security $security): void
    {
        $this->security = $security;
    }

    public function addFilterConstraint(ClassMetadata $targetEntity, string $targetTableAlias): string
    {
        if ($targetEntity->getReflectionClass()->getName() === Story::class && !$this->security->isGranted('ROLE_ADMIN')) {
            return sprintf('%s.active = 1', $targetTableAlias);
        }

        return '';
    }
}

Feel free to use, adapt and share. Leave a comment below and help me improve my articles.