使用 DataProvider 维护分页来过滤 api 平台中的集合

pri*_*spy 5 php pagination symfony api-platform.com

我们实现了一个RolePermissionChecker,它应该控制特定资源的安全性/可访问性 ( Offer) 比较资源的访问者和创建者的属性。因为itemOperations我们正在使用Voterhttps://api-platform.com/docs/core/security/#hooking-custom-permission-checks-using-voters),它工作得很好。

根据https://api-platform.com/docs/core/security/#filtering-collection-according-to-the-current-user-permissions应该collectionOperations DataProvider使用 - 所以我们实现了一个。

我们现在遇到的问题是OfferCollectionDataProvider首先调用它的父级ApiPlatform\Core\Bridge\Doctrine\Orm\CollectionDataProvider::getCollection函数以应用所有扩展并检索集合。然后我们过滤用户无权读取的实体。但是,当我们在准备好的页面上使用分页和过滤时,分页随后就会被破坏,例如一半的页面条目将被过滤,对于用户来说,看起来好像没有更多数据了。

有谁知道如何在不破坏分页且不使用扩展的情况下过滤集合,因为我们的权限检查有点复杂并且很难转换为查询?

<?php

namespace App\DataProvider;

use ApiPlatform\Core\Bridge\Doctrine\Orm\CollectionDataProvider;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator;
use App\Entity\Offer;
use App\Entity\User;
use App\Service\RolePermissionChecker;
use ArrayIterator;
use Doctrine\Persistence\ManagerRegistry;
use ReflectionProperty;
use Symfony\Component\Security\Core\Security;

/**
 * Class OfferCollectionDataProvider
 *
 * @package                  App\DataProvider
 */
class OfferCollectionDataProvider extends CollectionDataProvider
{
    /**
     * @var RolePermissionChecker
     */
    protected RolePermissionChecker $rolePermissionChecker;

    /**
     * @var Security $security
     */
    protected Security $security;

    /**
     * @param RolePermissionChecker $rolePermissionChecker
     * @param Security              $security
     * @param ManagerRegistry       $managerRegistry
     * @param iterable              $collectionExtensions
     */
    public function __construct(
        RolePermissionChecker $rolePermissionChecker,
        Security              $security,
        ManagerRegistry       $managerRegistry,
        iterable              $collectionExtensions = []
    )
    {
        parent::__construct(
            $managerRegistry,
            $collectionExtensions
        );

        $this->rolePermissionChecker = $rolePermissionChecker;
        $this->security              = $security;
    }

    /**
     * @param string      $resourceClass
     * @param string|null $operationName
     * @param array       $context
     *
     * @return bool
     */
    public function supports(
        string $resourceClass,
        string $operationName = null,
        array  $context = []
    ): bool
    {
        return Offer::class === $resourceClass;
    }

    /**
     * @inheritdoc
     */
    public function getCollection(
        string $resourceClass,
        string $operationName = null,
        array  $context = []
    ): iterable
    {
        $collection = parent::getCollection(
            $resourceClass,
            $operationName,
            $context
        );
        $user       = $this->security->getUser();

        if ($user instanceof User) {
            $hasReadPermissionsOnOffer = fn($offer) => $this->rolePermissionChecker->hasReadPermission(
                $offer,
                $user
            );

            if ($collection instanceof Paginator) {
                $newIteratorArray = [];

                foreach ($collection->getIterator() as $offer) {
                    if ($hasReadPermissionsOnOffer($offer)) {
                        $newIteratorArray[] = $offer;
                    }
                }

                $paginatorReflectionProperty = new ReflectionProperty(
                    Paginator::class,
                    'paginator'
                );

                $paginatorReflectionProperty->setAccessible(true);

                $paginator = $paginatorReflectionProperty->getValue($collection);

                return new FilteredPaginator(
                    $paginator,
                    new ArrayIterator(
                        $newIteratorArray
                    ),
                    $collection->getCurrentPage(),
                    $collection->getItemsPerPage(),
                    $collection->getLastPage(),
                    $collection->getTotalItems(),
                );
            } else if (
                is_array($collection)
            ) {
                $collection = array_filter(
                    $collection,
                    $hasReadPermissionsOnOffer,
                );
            }
        }

        return $collection;
    }
}
Run Code Online (Sandbox Code Playgroud)