symfony 如何定义API在放入数据之前查询相关表

vsikbqxv  于 2023-05-07  发布在  其他
关注(0)|答案(1)|浏览(130)

我需要一些关于如何构建API端点的建议。我使用的是Symfony 6.2和API Plaform 3.1。
假设我有以下3个实体:

  • 用户(id,...)
  • APIKey(id,userId,apikey,…)
  • Log(id,apikeyId,...)

所以每个用户都有一个APIKey。每个APIKey在一个Log表中可以有多个Log条目。
现在我想设置一个API端点,它执行以下任务:
1.检查apikey是否存在--〉如果apikey不存在则返回错误代码(404)
1.接收日志数据。如果现有日志条目超过5个,则重新使用最旧的日志条目。否则创建一个新的日志条目--〉完成后返回ok代码(200)
正如你所看到的,我需要做一些商业逻辑。
我的想法是这样设置一个端点:/api/log/{apikey}
使用body标签,我将发送日志数据
现在我不确定如何查询APIKey表中的apikey标识符。API平台项目提供了“关系”和“扩展”。这是要走的路,还是端点设计根本就不好?
谢谢你的建议,我应该在哪个方向做我的研究

os8fio9y

os8fio9y1#

我看到了这个用例的两个不同的实现,但是它们都没有使用扩展系统或嵌入式关系系统。
我已经实现了这个,它让你实现你自己的ProcessorInterface,并传递给它一个DTO。
首先,让我们在ApiKey资源中声明一个自定义的PATCH操作:

#[ApiResource(
    operations: [
        new GetCollection(),
        new Get(),
        new Post(),
        new Patch(),
        new Delete(),
        new Patch(
            uriTemplate: "/api_keys/{id}/log",
            normalizationContext: [
                "groups" => ["log:get:item-collection"],
            ],
            denormalizationContext: [
                "groups" => ["log:write"],
            ],
            input: Log::class,
            output: Log::class,
            processor: ApiKeyLogProcessor::class
        )
    ],
)]
class ApiKey {
    //...
    #[ORM\OneToMany(mappedBy: 'apiKey', targetEntity: Log::class, 
        cascade: ["persist", "remove"], orphanRemoval: true)
    ]
    private Collection $logs;
    
    //...

然后确保您的Log资源在您需要反序列化/序列化的属性上具有序列化组"log:write", "log:get:item-collection"

// src/Entity/Log.php

class Log {
    // ...
    #[ORM\Column(length: 255)]
    #[Groups(["log:get:item-collection", "log:post"])]
    private ?string $payload = null;
}

然后实现你的ProcessorInterface

<?php

namespace App\Processor;

use ApiPlatform\Doctrine\Common\State\PersistProcessor;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\ApiKey;
use App\Repository\ApiKeyRepository;

class ApiKeyLogProcessor implements ProcessorInterface
{
    private ProcessorInterface $decorated;
    private ApiKeyRepository $doctrine;

    public function __construct(
        PersistProcessor $decorated,
        ApiKeyRepository $doctrine,
    )
    {
        $this->decorated = $decorated;
        $this->doctrine = $doctrine;
    }

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
    {
        $apiKey = $this->getApiKeyFromRequestContext($context);
        $apiKey = $this->removeOldestLogFromApiKeyIfNeeded($apiKey);
        $apiKey->addLog($data);
        return $this->decorated->process($data, $operation, $uriVariables, $context);
    }

    private function getApiKeyFromRequestContext(array $context): ?ApiKey
    {
        /** @var ApiKey $clonedApiKey */
        $clonedApiKey = $context["previous_data"];
        $apiKeyId = $clonedApiKey->getId();
        return $this->doctrine->find($apiKeyId);
    }

    private function removeOldestLogFromApiKeyIfNeeded(ApiKey $apiKey): ApiKey
    {
        $apiKeyLogsSet = $apiKey->getLogs();
        if ($apiKeyLogsSet->count() === 5) {
            $oldestLog = $apiKeyLogsSet[0]; // TODO: implements better logic to retrieve the oldest log.
            $apiKey->removeLog($oldestLog); // "orphanRemoval: true" will remove the oldest log from database
        }
        return $apiKey;
    }
}

这个自定义路由的好处是,当ApiKey不存在时,它总是抛出错误404,并且总是在ApiKey不存在时返回成功200。成功。
另一种实现方式是在Log类上声明一个自定义的POST操作,并绑定到ProcessorInterface来完成与前一个操作相同的工作。请求的正文将是:

{ "payload": "my data...", "apiKey": "/api/api_keys/1" }

此操作的好处是它检索ApiKey资源而无需从ApiKeyRepository进行额外调用,但当ApiKey资源不存在时将抛出错误400,并且在成功时总是返回成功201。

相关问题