首页 > 解决方案 > 如何使用基于连接字段的 Doctrine 过滤器?

问题描述

我在我的一个应用程序中使用 ApiPlatform。我刚刚按照这个https://api-platform.com/docs/core/filters/#using-doctrine-orm-filters添加了一个基于登录用户的额外过滤器。

目的是用户只能看到自己的资源,并且效果很好,因为我使用 TokenStorageInterface 获得了当前登录用户的 id。

但是,当您想要过滤存储在 User 实体中的数据以外的其他内容时,如何执行此操作?我不想执行额外的 SQL 查询来检索所需的值。实际上,有时当 ApiPlatform 检索资源时,您不能使用过滤器,因为它缺少连接。所以我想通过添加一个额外的 INNER JOIN 来改变 ApiPlatform 生成的查询。

对这些表和相关资源进行映像:

我希望用户只检索其信息。使用 UserAware 类很容易在 User 和 Booking 上执行此操作,因为您拥有登录用户的 userId。但是为了防止有人阅读与其他用户相关的预订评论,我没有找到正确的方法,而在 booking.id = booking_commments.booking_id WHERE booking.user_id = %s 添加额外的内部连接很简单。

表 booking_comments 没有 user_id 字段,我不想将它添加到数据库中。我也不想使用 DataProvider 来构建自定义查询(这是我到目前为止所做的,我认为这不是当前的最先进技术)。

标签: symfonydoctrine-ormapi-platform.com

解决方案


我刚刚发现 Doctrine 过滤器与您的问题有关,但我认为它们不适合您的需求,因为 Api 平台利用其自己的扩展系统,该系统在安全页面中提到,这正是您想要做的:过滤您的结果集关于您当前的用户。此外,它可以让您更好地控制过滤(当前资源是什么?当前操作是什么?等)。

过滤所有 Booking 和 BookingComment URL 的结果集的简单示例:

class BookingExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
    /** @var Security */
    private $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
    {
        $this->filterResultSet($queryBuilder, $resourceClass);
    }

    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = [])
    {
        $this->filterResultSet($queryBuilder, $resourceClass);
    }

    private function filterResultSet(QueryBuilder $queryBuilder, string $resourceClass)
    {
            switch ($resourceClass) {
                case BookingComment::class:
                    $comment = $queryBuilder->getRootAliases()[0];
                    $booking = 'booking';
                    $queryBuilder->innerJoin("$comment.booking", $booking);
                    // do not break here !
                case Booking::class:
                    $booking = $booking ?? $queryBuilder->getRootAliases()[0];
                    $queryBuilder->innerJoin("$booking.user", 'user');
                    $queryBuilder->andWhere(':user = user');
                    $queryBuilder->setParameter(':user', $this->security->getUser());
            }
    }
}

请注意,此示例考虑禁止匿名用户。


推荐阅读