如何在api平台中实现带有过滤功能的自定义item get端点?

tob*_*old 3 php api endpoint symfony api-platform.com

我正在开发一个 symfony/api 平台应用程序,允许用户跟踪体育比赛。我的实体看起来像这样(为简洁起见缩短):

用户.php

class User implements UserInterface
{
    // ...

    /**
     * @ORM\OneToMany(targetEntity=MatchPlayer::class, mappedBy="user")
     */
    private $matches;

    // ...
}
Run Code Online (Sandbox Code Playgroud)

MatchPlayer.php

class MatchPlayer
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity=User::class, inversedBy="matches")
     * @ORM\JoinColumn(onDelete="SET NULL")
     */
    private $user;

    /**
     * @ORM\ManyToOne(targetEntity=Match::class, inversedBy="players")
     */
    private $playedMatch;

    /**
     * @ORM\ManyToOne(targetEntity=Position::class, inversedBy="matches")
     */
    private $position;

    // ...
}
Run Code Online (Sandbox Code Playgroud)

匹配.php

class Match
{
    // ...

    /**
     * @ORM\Column(type="smallint")
     * @Groups({"match:read"})
     */
    private $outcome;

    /**
     * @ORM\ManyToOne(targetEntity=Sport::class, inversedBy="matches")
     */
    private $sport;

    /**
     * @ORM\OneToMany(targetEntity=MatchPlayer::class, mappedBy="playedMatch", cascade={"persist", "remove"})
     */
    private $players;

    // ....
}
Run Code Online (Sandbox Code Playgroud)

因此,在我的模型中,用户可以与许多比赛相关,而一场比赛可以通过粘合表与许多用户相关,该粘合表还保存用户所踢的位置。

现在我想公开一个带有/api/users/{id}/statistics或之类的 api 平台的端点/api/statistics/{userId},该平台动态获取数据并显示用户在哪项运动中参加了多少场比赛、在什么位置以及用户赢得/平局/输掉了多少场比赛。理想情况下,端点将允许按运动进行过滤,并且看起来像这样/api/users/{id}/statistics?sport[]=football&sport[]&outcome=win

由于这些统计信息不会作为实体持久保存到数据库中,因此我尝试了一种类似于在没有任何路由文档页面的情况下公开模型的方法。我创建了一个Statistics如下所示的实体:

/**
 * @ApiResource(
 *     collectionOperations={},
 *     itemOperations={
 *          "get"={
 *              "controller"=NotFoundAction::class,
 *              "read"=false,
 *              "output"=false,
 *          },
 *     }
 * )
 */
class Statistic
{
    /**
     * @var User
     * @ApiProperty(identifier=true)
     */
    public $user;

    /**
     * @var Position[]|null
     */
    public $position = [];

    /**
     * @var Sport[]|null
     */
    public $maps = [];

    /**
     * @var int
     */
    public $wins = 0;

    /**
     * @var int
     */
    public $ties = 0;

    /**
     * @var int
     */
    public $losses = 0;
}
Run Code Online (Sandbox Code Playgroud)

并向User实体添加自定义操作:

 * @ApiResource(
 *    ...
 *     itemOperations={
 *          "get_statistic"={
 *              "method"="GET",
 *              "path"="/users/{id}/statistics",
 *          }
 *     },
 *    ...
 */
Run Code Online (Sandbox Code Playgroud)

然而我不知道如何实现按运动、位置和胜/平/负进行过滤。据我所知,“普通”过滤器不起作用,因为它仅适用于集合的获取操作。

如果这可能的话,我将如何在我的 api 中实现它?我已经尝试过自定义数据提供程序和控制器,但我无法在任一解决方案中获取过滤器查询参数,并且“普通”过滤器(如 SearchFilter 中内置的 api 平台)将无法工作,因为它仅应用于集合上的 get 操作,并且我正在处理一个项目。

red*_*ecs 6

这绝对是可能的,但根据您的选择,您需要做更多的工作才能获得所需的结果。

我将使用自定义操作,因为这更容易解释,并且我已经有一些代码示例。

要获取过滤所需的信息,您需要采用较低级别的方法。您错过的关键部分是 API 平台构建在 Symfony 之上,因此您只需使用 (Request对于自定义操作)或RequestStack(对于数据提供者)来获取过滤器。

另外,为了确保 API Platform 知道如何序列化您输出的数据(Statistics对象),您需要使用DTO

代码如下所示:

在您的实体上,我们添加自定义操作类并将输出指定为统计类:

 * @ApiResource(
 *    ...
 *     itemOperations={
 *          "get_statistics"={
 *              "method"="GET",
 *              "path"="/users/{id}/statistics",
 *              "controller"=UserStatsAction::class,
 *              "input"=Statistics::class
 *          }
 *     },
 *    ...
 */
Run Code Online (Sandbox Code Playgroud)

自定义操作代码示例:

final class UserStatsAction
{
    private $em;


    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    public function __invoke(Request $request)
    {
        $id = $request->get('id');
        $repository = $this->em->getRepository(User::class);
        if(!($user = $repository->find($id))) {
            throw new NotFoundHttpException();
        }

        $sports = $request->query->get('sport', []);
        $outcome = $request->query->get('outcome');

        // Optional: validate your filter data
        $validator = Validation::createValidator();
        $context = $validator->startContext();
        $context->atPath('sports')->validate($sports, [
            new Assert\Choice([
                'choices' => ['football', 'basketball'],
            ]),
        ]);
        $context->atPath('outcome')->validate($outcome, [
            new Assert\Choice([
                'choices' => ['win', 'loose', 'tie'],
            ]),
        ]);
        $violations = $context->getViolations();

        if (0 !== count($violations)) {
            throw new ValidationException($violations);
        }

        // I'll assume you are hnadiling empty/nulls value properly inside this method
        // and return all the stats if 
        $results = $repository->getStatistics($sports, $outcome);

        // For this to work, you'll need to set a DTO for your stats
        return $results;
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用Requestas 参数进行自定义操作,而不是User实体。我的示例中有一些您可能不需要的代码,例如从存储库中获取用户或验证过滤器(不过,我确实鼓励用户输入清理/验证)。

值得一提的是:API 平台不鼓励自定义操作,并且您将失去 GraphQL 支持。如果您需要 GraphQL,可以使用 a 来完成相同的结果,DataProvider但这是一个更高级的设置,我需要模拟您应用程序的某些部分才能弄清楚。

希望这可以帮助。

更新:

为了使过滤器正常工作,您还需要更新 OpenAPI/Swagger 配置,正如tobias ingold在下面的评论中指出的那样。

您可以使用 PHP 并创建规范化器来做到这一点,如文档的覆盖 OpenAPI 规范部分中所述。

这也可以通过扩展注释来完成APIResource,下面是一个示例:

 * @ApiResource(
 *     ...
 *     collectionOperations={
 *          "post",
 *          "get"={
 *              "openapi_context"={
 *                  "parameters"={
 *                      {
 *                          "name": "<query_string_param_name>",
 *                          "type": "string",
 *                          "in": "query",
 *                          "required": false,
 *                          "description": "description",
 *                          "example": ""
 *                      }
 *                  }
 *              }
 *          }
 *     }
 *     ...
 *  })
Run Code Online (Sandbox Code Playgroud)

我发现这种方法更容易使用,但没有记录。我根据我的 OpenAPI 规范知识和官方文档中的配置接收上传文件的实体示例推断出这一点。