check24/apitk-url-bundle

此包为RESTful API提供过滤、排序和分页功能

安装次数: 1,942

依赖项: 0

建议者: 0

安全性: 0

星标: 4

关注者: 1

分支: 11

类型:symfony-bundle


README

安装

使用composer安装包

composer require check24/apitk-url-bundle

用法

定义可能的过滤器、排序和分页

过滤

您可以在操作注解中指定哪些字段应该由客户端进行过滤

use Shopping\ApiTKUrlBundle\Annotation as ApiTK;

/**
 * Returns the users in the system.
 *
 * @Rest\Get("/v1/users")
 * @Rest\View()
 *
 * @ApiTK\Filter(name="username")
 * @ApiTK\Filter(name="created", allowedComparisons={"gt","lt"})
 * @ApiTK\Filter(name="active", enum={"true","false"})
 * @ApiTK\Filter(name="country", queryBuilderName="a.country")
 *
 * @return User[]
 */

使用allowedComparisonsenum选项限制用户的可能性。

如果您想使用内置的查询构建器应用程序,并且实体字段名与过滤器字段名不同(例如,因为它是一个带有别名的连接表中的字段),请使用queryBuilderName选项。

客户端现在可以调用API端点,并带有过滤器选项,如GET /v1/users?filter[created][gt]=2018-01-01&filter[country][in]=DE,AT。如果客户端指定了无效的过滤器(未配置/允许的字段或比较),客户端将收到400响应。

您还可以使用路由参数进行过滤输入。只需声明与路由占位符同名的过滤器即可。

/**
 * Returns the addresses for the given user.
 *
 * @Rest\Get("/v1/users/{id}/addresses")
 * @Rest\View()
 *
 * @ApiTK\Filter(name="id", queryBuilderName="u.id")
 *
 * @return Address[]
 */

排序

您可以在操作注解中指定哪些字段应该由客户端进行排序

use Shopping\ApiTKUrlBundle\Annotation as ApiTK;

/**
 * Returns the users in the system.
 *
 * @Rest\Get("/v1/users")
 * @Rest\View()
 *
 * @ApiTK\Sort(name="username")
 * @ApiTK\Sort(name="zipcode", queryBuilderName="a.zipCode", allowedDirections={"asc"})
 *
 * @return User[]
 */

使用allowedDirections选项限制用户的可能性(例如,如果您只想支持升序排序)。

如果您想使用内置的查询构建器应用程序,并且实体字段名与排序字段名不同(例如,因为它是一个带有别名的连接表中的字段),请使用queryBuilderName选项。

客户端现在可以调用API端点,并带有排序选项,如GET /v1/users?sort[zipcode]=asc&sort[username]=desc。如果客户端指定了无效的排序(未配置/允许的字段或方向),客户端将收到400响应。

分页

您可以在操作注解中指定结果是否应该由客户端进行分页

use Shopping\ApiTKUrlBundle\Annotation as ApiTK;

/**
 * Returns the users in the system.
 *
 * @Rest\Get("/v1/users")
 * @Rest\View()
 *
 * @ApiTK\Pagination
 * Or:
 * @ApiTK\Pagination(maxEntries=25)
 *
 * @return User[]
 */

如果您想限制客户端每页获取的项目数量,您可以指定maxEntries选项。但请只添加一个Pagination注解(不要像上面的示例那样)。

客户端现在可以调用API端点,并带有限制选项,如GET /v1/users?limit=10(获取前10个条目)或GET /v1/users?limit=30,10(获取带有偏移量30(=第4页)的10个条目)。如果客户端尝试分页而您没有启用,或者客户端想要获取比您指定的更多的项目,客户端将收到400响应。

此外,响应中还发送了一个新的头x-apitk-pagination-total,其中包含条目的总数,以便客户端可以调整其分页按钮。

访问客户端输入

通过参数转换器自动加载数组

您可以通过轻松注入到您的操作中,自动获取所有过滤器在正确顺序和分页子集中实体结果数组。

首先在您的doctrine.yaml中将默认存储库设置为我们的ApiToolkitRepository

doctrine:
  orm:
    default_repository_class: Shopping\ApiTKUrlBundle\Repository\ApiToolkitRepository

对于所有自定义存储库,从ApiToolkitRepository扩展它们(或者如果您想使它们可注入,从ApiToolkitServiceRepository扩展,它从ServiceEntityRepository扩展,因此请确保以正确的方式实现构造函数)以便它们也获得所需的功能。

之后,将一个 @ApiTK\Result 注解添加到您的控制器操作中。操作参数将自动填充给定实体存储库的过滤、排序和分页结果集。

//ItemController.php
/**
 * @ApiTK\Filter(name="name")
 * @ApiTK\Sort(name="name")
 * @ApiTK\Pagination
 *
 * @ApiTK\Result("items", entity="App\Entity\Item")
 */
public function getItems(array $items)
{
    return $items;
}

如果您需要为联接的实体中的字段进行过滤/排序,只需在自定义实体存储库中定义自己的 findByRequest() 方法。

//UserRepository.php
use Shopping\ApiTKUrlBundle\Repository\ApiToolkitRepository;
class UserRepository extends ApiToolkitRepository
{
    public function findByRequest(ApiService $apiService): array
    {
        $qb = $this->createQueryBuilder('u');
        $qb->leftJoin('u.addresses', 'a')->distinct();

        $apiService->applyToQueryBuilder($qb);

        return $qb->getQuery()->getResult();
    }
}
//UserController.php
/**
 * @ApiTK\Filter(name="username")
 * @ApiTK\Filter(name="country", queryBuilderName="a.country")
 * @ApiTK\Pagination
 *
 * @ApiTK\Result("users", entity="App\Entity\User")
 */
public function getUsers(array $users)
{
    return $users;
}

如果您需要为存储库添加不同的方法,这些方法可以通过 Result 注解执行,那么您可以将 methodName="findBySomethingElse" 参数添加到注解中。它将然后在您的存储库中查找此方法,而不是默认的 findByResult() 方法。请确保只接受 ApiService 作为唯一参数。

因此,这也可行

//UserRepository.php
use Shopping\ApiTKUrlBundle\Repository\ApiToolkitRepository;
class UserRepository extends ApiToolkitRepository
{
    public function findBarBaz(ApiService $apiService): array
    {
        // your own logic
    }
}
//UserController.php
/**
 * @ApiTK\Result("users", entity="App\Entity\User", methodName="findBarBaz")
 */
public function getUsers(array $users)
{
    return $users;
}

注意:如果您的注解中启用了分页器,则查询将通过您的存储库中的 applyToQueryBuilder() 方法执行以确定总计数。请确保在构建查询的其他部分之后调用此方法。

如果您需要使用默认以外的实体管理器/连接,可以通过添加 entityManager="foobar" 到您的注解中指定实体管理器。

手动访问

如果您必须实现带有过滤、排序和分页的定制逻辑,您还可以注入 ApiService 并使用其方法。

//UserController.php
public function getUsersV1(EntityManagerInterface $entityManager, ApiService $apiService)
{
    $users = $entityManager->getRepository(User::class)->findAll();

    //Filtering
    if ($apiService->hasFilteredField('username')) {
        $usernameFilter = $apiService->getFilteredField('username');
        $users = array_filter($users, function($user) use ($usernameFilter) {
            if ($usernameFilter->getComparison() === ApiTK\Filter::COMPARISON_EQUALS) {
                return $user->getUsername() === $usernameFilter->getValue();
            }
            return false;
        });
    }
    /*...*/

    //Sorting
    foreach (array_reverse($apiService->getSortedFields()) as $sortField) {
        if ($sortField->getName() === 'username') {
            usort($users, function($user1, $user2) use ($sortField) {
                if ($sortField->getDirection === ApiTK\Sort::ASCENDING) {
                    return $user1->getUsername() <=> $user2->getUsername();
                } else {
                    return $user2->getUsername() <=> $user1->getUsername();
                }
            });
        }
        /*...*/
    }

    //Pagination
    $apiService->setPaginationTotal(count($users));
    $users = array_slice($users, $apiService->getPaginationOffset(), $apiService->getPaginationLimit());

    return $users;
}

手动实现一些过滤器

如果您有一些需要一些自定义逻辑的“虚拟”字段,您可以使用 applyToQueryBuilder 并
仍然手动定义您的字段。

假设您想要实现一个搜索参数。这个搜索将查看用户名和电子邮件。

//UserController.php
/**
 * @ApiTK\Filter(name="search", autoApply=false)
 *
 * @ApiTK\Result("users", entity="App\Entity\User")
 */
public function getUsers(array $users)
{
    return $users;
}

为了使参数可用,您需要注册一个新的 Filter 字段。必须为此过滤器设置 autoApply=false,因为没有“搜索”字段在实体上,我们希望我们自己组装这部分查询。

//UserRepository.php
use Shopping\ApiTKUrlBundle\Repository\ApiToolkitRepository;
class UserRepository extends ApiToolkitRepository
{
    public function findByRequest(ApiService $apiService): array
    {
        $qb = $this->createQueryBuilder('u');

        if ($apiService->hasFilteredField('search')) {
            $search = $apiService->getFilteredField('search');
            $qb->andWhere(
                $qb->expr()->orX(
                    $qb->expr()->like('u.username', ':query'),
                    $qb->expr()->like('u.email', ':query')
                )
            )->setParameter('query', '%' . $search . '%');
        }

        $apiService->applyToQueryBuilder($qb);

        return $qb->getQuery()->getResult();
    }
}

applyToQueryBuilder 将跳过我们的 autoApply=false 字段,因此我们可以自己添加它。

重要:当使用分页器时,在手动过滤后调用 $apiService->applyToQueryBuilder() 方法,以便分页器可以在过滤后的结果上构建总计数标头。否则,您的标头中会有不正确的值。

手动实现排序

与手动过滤属性相同,您可以像上面那样实现手动排序

//UserController.php
/**
 * @ApiTK\Sort(name="mySortProperty", autoApply=false)
 *
 * @ApiTK\Result("users", entity="App\Entity\User")
 */
public function getUsers(array $users)
{
    return $users;
}
//UserRepository.php
use Shopping\ApiTKUrlBundle\Repository\ApiToolkitRepository;
class UserRepository extends ApiToolkitRepository
{
    public function findByRequest(ApiService $apiService): array
    {
        $qb = $this->createQueryBuilder('u');

        $apiService->applyToQueryBuilder($qb);

        if ($apiService->hasSortedField('mySortProperty')) {
            // perform your own sorting logic for this field
        }

        return $qb->getQuery()->getResult();
    }
}

对于更高级的用途,请参阅上面的“手动访问”部分。

文档

定义的过滤器、排序和分页将自动添加到 NelmioApiDoc 输出(即 Swagger UI)中。您不必担心这一点。