symfonydoctrine-orm

Symfony: setter injection not working in a custom Doctrine filter


To make sure that every user gets only data that belongs to its account, I tried to create a doctrine filter that inject Security service to it using a setter (setSecurity) and #[Required] attribute, and then in addFilterConstraint method, I get the user and create needed constraint SQL, but unfortunately for some reason setter injection not working and setSecurity never executed!

I'm using Symfony 6.3.4

<?php

namespace App\Doctrine\Filter;

use App\Entity\User;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Contracts\Service\Attribute\Required;

class AccountFilter extends SQLFilter
{
    private ?Security $security = null;

    #[Required]
    public function setSecurity(Security $security)
    {
        $this->security = $security;
    }

    public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
    {
        // check if the entity has the account field
        if (!$targetEntity->hasAssociation('account')) {
            return '';
        }

        $accountColumnName = $targetEntity->getSingleAssociationJoinColumnName('account');

        if ($this->security === null) {
            return '';
        }

        $user = $this->security->getUser();
        if (!$user instanceof User) {
            return '';
        }

        return sprintf('%s.%s = %s', $targetTableAlias, $accountColumnName, $user->getAccount()->getId());
    }
}

Solution

  • Please see Az.Youness' answer, it is better.

    Sadly, the FilterCollection that handles filters and that is instantiated by probably doctrine only receives the class names of the filter and instantiates it when enabled (which probably happens automatically).

    Since as far as I know doctrine and symfony only use one entity manager, and apparently the filter collection is kept between queries, you might be able to inject your Security by getting your filter from the entitymanager in a kernel event listener (where you can request the Security service via standard dependency injection) or some other way and setSecurity explicitly then.

    $entityManager->getFilters()->getFilter("the filter name")->setSecurity($security);
    

    However, when the filter is disabled, it will be removed completely and instantiated again when enabled again, according to source code. You would have to use suspend/restore instead, if you ever want to "disable" the filter for a number of queries. Which might be relevant for you or not.