如何使用基于 join 字段的 Doctrine 过滤器?

How to use Doctrine filters based on join field?

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

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

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

图像那些 table 和相关资源:

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

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

我刚刚发现 Doctrine 过滤器与你的问题有关,但我认为它们不适合你的需求,因为 Api 平台利用它自己的 extensions system, which is mentionned in the security 页面,这正是你想要做的:过滤关于您当前用户的结果集。此外,它可以让您更好地控制(当前资源是什么?当前操作是什么?等等)过滤。

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

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());
            }
    }
}

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