Symfony:setter注入在自定义Doctrine过滤器中不工作

6pp0gazn  于 2023-10-23  发布在  其他
关注(0)|答案(2)|浏览(122)

为了确保每个用户只获得属于其帐户的数据,我尝试创建一个原则过滤器,使用setter(setSecurity)和#[Required]属性将Security服务注入其中,然后在addFilterConstraint方法中,我获取用户并创建所需的约束SQL,但不幸的是,由于某种原因,setter注入不起作用,setSecurity从未执行!
我用的是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());
    }
}
km0tfn4u

km0tfn4u1#

遗憾的是,处理过滤器的FilterCollection由probably原则示例化,它只接收过滤器的类名,并在启用时示例化它(这可能是自动发生的)。
因为据我所知,doctrine和symfony只使用 * 一个 * 实体管理器,显然过滤器集合是在查询之间保存的,你可以通过从内核事件监听器中的实体管理器获取过滤器来注入你的Security(在那里你可以通过标准依赖注入请求Security服务)或其他方式,然后显式地设置Security。

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

然而,根据源代码,当过滤器被 * 禁用 * 时,它将被完全删除,并在再次 * 启用 * 时再次示例化。如果您想要为许多查询“禁用”过滤器,则必须使用suspend/restore。这可能与您相关或无关。

ndh0cuux

ndh0cuux2#

这是我基于@Jakumi的answer提出的解决方案:
因此,我们可以创建一个请求事件侦听器,在必要时启用过滤器,而不是尝试将Security服务注入到过滤器类中:

<?php

namespace App\EventListener;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;

#[AsEventListener(event: RequestEvent::class)]
class AccountFilterEnablerListener
{
    public function __construct(
        private Security $security,
        private EntityManagerInterface $em,
    ) {
    }

    public function onKernelRequest(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            return;
        }

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

        if (!$user instanceof User) {
            return;
        }

        $this->em->getFilters()->enable('account_filter')->setParameter('account', $user->getAccount()->getId());
    }
}

AccountFilter类看起来像这样:

<?php

namespace App\Doctrine\Filter;

use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;

class AccountFilter extends SQLFilter
{

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

        return sprintf(
           '%s.%s = %s',
           $targetTableAlias,
           $targetEntity->getSingleAssociationJoinColumnName('account'), // account_id
           $this->getParameter('account')
        );
    }
}

相关问题