如何使用基于 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 和相关资源:
- 用户:/api/users[/{id}]
- 预订:/api/bookings[/{id}]
- booking_comments: /api/booking/comments[/{id}]
我希望用户只检索其信息。
使用 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());
}
}
}
请注意,此示例考虑禁止匿名用户。
我在我的一个应用程序中使用 ApiPlatform。我刚刚按照这个 https://api-platform.com/docs/core/filters/#using-doctrine-orm-filters 添加了一个基于登录用户的额外过滤器。
目的是用户只能看到自己的资源,而且效果很好,因为我使用 TokenStorageInterface 获得了当前登录用户的 ID。
但是,当您想过滤用户实体中存储的数据以外的其他内容时,该怎么做呢?我不想执行额外的 SQL 查询来检索所需的值。事实上,有时当 ApiPlatform 检索资源时,您不能使用过滤器,因为它缺少连接。所以我想通过添加一个额外的 INNER JOIN 来改变 ApiPlatform 生成的查询。
图像那些 table 和相关资源:
- 用户:/api/users[/{id}]
- 预订:/api/bookings[/{id}]
- booking_comments: /api/booking/comments[/{id}]
我希望用户只检索其信息。 使用 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());
}
}
}
请注意,此示例考虑禁止匿名用户。