intellect-web-development/symfony-doctrine-search-bundle

通过HTTP API搜索您的doctrine实体库

1.0.2 2024-05-14 07:42 UTC

This package is auto-updated.

Last update: 2024-09-16 08:34:04 UTC


README

描述

本包旨在方便地使用symfony应用程序的表示层。

用途

  • 验证API方法输入参数的有效性
  • 根据OpenApi兼容的DTO生成Swagger文档
  • 对doctrine实体进行排序、过滤和分页
  • 基于doctrine实体获取资源/聚合

过滤示例

查询字符串示例

GET /clients?filter[emails.email][like]=26d@&sort=-createdAt,updatedAt&page[number]=1&page[size]=20&filter[userId][eq]=ccf92b7a-8e05-4f4b-9f0a-e4360dbacb23&filter[name.translations.last][eq]=Tesla&lang=ru

排序

描述

默认排序通过"sort"参数设置。排序方向由属性名称前可选的'-'符号指定。如果存在'-',则按该字段进行降序排序,否则按升序排序。允许对多个聚合字段进行排序。为此,需要写入多个字段,并用逗号分隔。越早指定的字段,在查询时权重越大。

示例

sort='-createdAt,updatedAt'

分页

默认分页通过"page"参数设置。参数包含两个字段 - number和size。

  • "number"指定客户端请求的页面号。默认:1
  • "size"指定页面大小(应显示多少个聚合)。默认:20

描述

page[number]='1'
page[size]='20'

过滤

描述

搜索运算符

示例

filter[userId][eq]='ccf92b7a-8e05-4f4b-9f0a-e4360dbacb23'
filter[name.translations.last][eq]='Tesla'
filter[emails.email][like]='26d@'
filter[userId][eq]='ccf92b7a-8e05-4f4b-9f0a-e4360dbacb23'
filter[name.translations.last][eq]='Tesla'
filter[emails.email][in][]='0791d11b6a952a3804e7cb8a220d0a9b@mail.ru'
filter[emails.email][in][]='0891d11b6a952a3804e7cb8a220d0a9b@mail.ru'

代码示例

查询

定义

查询 - 获取当前实体状态(资源/聚合)的请求,不改变其状态。

聚合

获取聚合数据的请求。

读取操作示例

<?php

declare(strict_types=1);

namespace App\Http\User\Read;

use App\Entity\User;
use App\Http\User\CommonOutputContract;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security;
use OpenApi\Annotations as OA;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use IWD\SymfonyDoctrineSearch\Dto\Input\OutputFormat;
use IWD\SymfonyDoctrineSearch\Dto\Output\ApiFormatter;
use IWD\SymfonyDoctrineSearch\Service\Presenter;
use IWD\SymfonyDoctrineSearch\Service\QueryBus\Aggregate\Bus;
use IWD\SymfonyDoctrineSearch\Service\QueryBus\Aggregate\Query;

class Action
{
    /**
     * @OA\Tag(name="User")
     * @OA\Response(
     *     response=200,
     *     description="Read User",
     *     @OA\JsonContent(
     *          allOf={
     *              @OA\Schema(ref=@Model(type=ApiFormatter::class)),
     *              @OA\Schema(type="object",
     *                  @OA\Property(
     *                      property="data",
     *                      ref=@Model(type=CommonOutputContract::class)
     *                  ),
     *                  @OA\Property(
     *                      property="status",
     *                      example="200"
     *                 )
     *             )
     *         }
     *     )
     * )
     * @OA\Response(
     *     response=400,
     *     description="Bad Request"
     * ),
     * @OA\Response(
     *     response=401,
     *     description="Unauthenticated",
     * ),
     * @OA\Response(
     *     response=403,
     *     description="Forbidden"
     * ),
     * @OA\Response(
     *     response=404,
     *     description="Resource Not Found"
     * )
     * @Security(name="Bearer")
     */
    #[Route(
        data: '/users/{id}.{_format}',
        name: 'users.read',
        defaults: ['_format' => 'json'],
        methods: ['GET']
    )]
    public function read(string $id, Bus $bus, OutputFormat $outputFormat, Presenter $presenter): Response
    {
        $query = new Query(
            aggregateId: $id,
            targetEntityClass: User::class
        );

        /** @var User $user */
        $user = $bus->query($query);

        return $presenter->present(
            data: ApiFormatter::prepare(
                CommonOutputContract::create($user)
            ),
            outputFormat: $outputFormat
        );
    }
}

搜索操作示例

<?php

declare(strict_types=1);

namespace App\Http\User\Search;

use App\Entity\User;
use App\Http\User\CommonOutputContract;
use IWD\SymfonyDoctrineSearch\Service\Presenter;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security;
use OpenApi\Annotations as OA;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use IWD\SymfonyDoctrineSearch\Dto\Input\OutputFormat;
use IWD\SymfonyDoctrineSearch\Dto\Input\SearchQuery;
use IWD\SymfonyDoctrineSearch\Dto\Output\ApiFormatter;
use IWD\SymfonyDoctrineSearch\Dto\Output\OutputPagination;
use IWD\SymfonyDoctrineSearch\Service\QueryBus\Search\Bus;
use IWD\SymfonyDoctrineSearch\Service\QueryBus\Search\Query;

class Action
{
    /**
     * @OA\Tag(name="User")
     * @OA\Get(
     *     @OA\Parameter(
     *          name="searchQuery",
     *          in="query",
     *          required=false,
     *          @OA\Schema(
     *              ref=@Model(type=QueryParams::class)
     *          ),
     *     )
     * )
     * @OA\Response(
     *     response=200,
     *     description="Search by Users",
     *     @OA\JsonContent(
     *          allOf={
     *              @OA\Schema(ref=@Model(type=ApiFormatter::class)),
     *              @OA\Schema(
     *                  type="object",
     *                  @OA\Property(
     *                      property="data",
     *                      type="object",
     *                      @OA\Property(
     *                          property="data",
     *                          ref=@Model(type=CommonOutputContract::class),
     *                          type="object"
     *                      ),
     *                      @OA\Property(
     *                          property="pagination",
     *                          ref=@Model(type=OutputPagination::class),
     *                          type="object"
     *                      )
     *                  ),
     *                  @OA\Property(
     *                      property="status",
     *                      example="200"
     *                 )
     *             )
     *         }
     *     )
     * )
     * @OA\Response(
     *     response=400,
     *     description="Bad Request"
     * ),
     * @OA\Response(
     *     response=401,
     *     description="Unauthenticated",
     * ),
     * @OA\Response(
     *     response=403,
     *     description="Forbidden"
     * ),
     * @OA\Response(
     *     response=404,
     *     description="Resource Not Found"
     * )
     * @Security(name="Bearer")
     */
    #[Route(
        data: '/users.{_format}',
        name: 'users.search',
        defaults: ['_format' => 'json'],
        methods: ['GET']
    )]
    public function search(
        Bus          $bus,
        SearchQuery  $searchQuery,
        OutputFormat $outputFormat,
        Presenter    $presenter
    ): Response {
        $query = new Query(
            targetEntityClass: User::class,
            pagination: $searchQuery->pagination,
            filters: $searchQuery->filters,
            sorts: $searchQuery->sorts
        );

        $searchResult = $bus->query($query);
        return $presenter->present(
            data: ApiFormatter::prepare([
                'data' => array_map(static function (User $user) {
                    return CommonOutputContract::create($user);
                }, $searchResult->entities),
                'pagination' => $searchResult->pagination
            ]),
            outputFormat: $outputFormat
        );
    }
}

SearchQueryParams示例

<?php

declare(strict_types=1);

namespace App\Http\User\Search;

use OpenApi\Annotations as OA;
use IWD\SymfonyDoctrineSearch\Dto\Input\Filters;
use IWD\SymfonyDoctrineSearch\Dto\Input\SearchQuery;

class QueryParams extends SearchQuery
{
    /**
     * @OA\Property(
     *     property="filter",
     *     type="object",
     *     example={
     *         "id": {"eq": "ab4ac777-e054-45ec-b997-b69062917d10"},
     *         "createdAt": {"range": "2022-02-22 12:00:00,2022-02-22 14:00:00"},
     *         "updatedAt": {"range": "2022-02-22 12:00:00,2022-02-22 14:00:00"},
     *         "email": {"eq": "user@dev.ru"},
     *         "status": {"eq": "active"}
     *     }
     * )
     */
    public Filters $filters;
}

Command-action示例

<?php

declare(strict_types=1);

namespace App\Http\User\Create;

use App\Http\User\CommonOutputContract;
use App\Entity\User\UseCase\Create\Handler;
use IWD\SymfonyDoctrineSearch\Service\Presenter;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security;
use OpenApi\Annotations as OA;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use IWD\SymfonyDoctrineSearch\Dto\Input\OutputFormat;
use IWD\SymfonyDoctrineSearch\Dto\Output\ApiFormatter;

class Action
{
    /**
     * @OA\Tag(name="Auth.User")
     * @OA\Post(
     *     @OA\RequestBody(
     *         @OA\MediaType(
     *             mediaType="application/json",
     *             @OA\Schema(
     *                 ref=@Model(type=InputContract::class)
     *             )
     *         )
     *     )
     * )
     * @OA\Response(
     *     response=200,
     *     description="Create User",
     *     @OA\JsonContent(
     *          allOf={
     *              @OA\Schema(ref=@Model(type=ApiFormatter::class)),
     *              @OA\Schema(type="object",
     *                  @OA\Property(
     *                      property="data",
     *                      ref=@Model(type=CommonOutputContract::class)
     *                  ),
     *                  @OA\Property(
     *                      property="status",
     *                      example="200"
     *                 )
     *             )
     *         }
     *     )
     * )
     * @OA\Response(
     *     response=400,
     *     description="Bad Request"
     * ),
     * @OA\Response(
     *     response=401,
     *     description="Unauthenticated",
     * ),
     * @OA\Response(
     *     response=403,
     *     description="Forbidden"
     * ),
     * @OA\Response(
     *     response=404,
     *     description="Resource Not Found"
     * )
     * @Security(name="Bearer")
     */
    #[Route(
        data: '/users/create.{_format}',
        name: 'users.create',
        defaults: ['_format' => 'json'],
        methods: ['POST']
    )]
    public function create(
        OutputFormat $outputFormat,
        InputContract $contract,
        Handler $handler,
        Presenter $presenter
    ): Response {
        $user = $handler->handle(
            $contract->createCommand()
        );

        return $presenter->present(
            data: ApiFormatter::prepare(
                data: CommonOutputContract::create($user),
                messages: ['User created']
            ),
            outputFormat: $outputFormat
        );
    }
}

InputContract示例

<?php

declare(strict_types=1);

namespace App\Http\User\Create;

use App\Entity\User\ValueObject\Email as UserEmail;
use App\Entity\User\UseCase\Create\Command;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotNull;
use IWD\SymfonyDoctrineSearch\Interfaces\InputContractInterface;

class InputContract implements InputContractInterface
{
    #[NotNull]
    #[Length(min: 3, max: 255)]
    public string $password;

    #[NotNull]
    #[Email]
    #[Length(max: 255)]
    public string $email;

    public function createCommand(): Command
    {
        return new Command(
            password: $this->password,
            email: new UserEmail($this->email)
        );
    }
}

OutputContract示例

<?php

declare(strict_types=1);

namespace App\Http\Contract\User;

use App\Entity\User;
use DateTimeInterface;

class CommonOutputContract
{
    public string $id;
    public string $createdAt;
    public string $updatedAt;
    public string $email;
    public string $status;
    public string $role;

    public static function create(User $user): self
    {
        $contract = new self();
        $contract->id = $user->getId()->getValue();
        $contract->createdAt = $user->getCreatedAt()->format(DateTimeInterface::ATOM);
        $contract->updatedAt = $user->getUpdatedAt()->format(DateTimeInterface::ATOM);
        $contract->email = $user->getEmail()->getValue();
        $contract->status = $user->getStatus();
        $contract->role = $user->getRole()->getName();

        return $contract;
    }
}