Published on

Api platform 教程11---自定义操作

原创文章,转载时需取得本人同意并注明来源
Authors
  • Api platform 教程11---自定义操作
    Name
    langziyang
    Twitter

Api platform 教程11---自定义操作

我们的代码越来越有趣了,一个完整而又简单的的CRUD一点问题都没有了,可是现实中我们肯定还有别的要求,比如上传文件、以及在 除了默认端点以外再写一个自己的端点等等。

在普通的程序中我们上传文件可能是先把文件放在input中,然后提交表单处理,可是在常见的API中,我们可能会自动上传图片, 然后再把返回的图片地址放在数据中提交。

如果我们去看api platform文档,它的方法和我接下来的方式可能会有差别。

在api platform3.3以前,官方建议写一个Controller来处理,但无论什么时候,自定义Controller都是不建议的,你应该首先想到 Processor和Provider来处理你的流程。

另一个问题又来了:上传文件我们是一个在依附于任何一个Entity的操作,所以我应该写在哪一个Entity上呢?Api platform也 可以公开一个非实体的文件,

#src/ApiResource/Common.php
<?php

namespace App\ApiResource;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use App\State\Common\Processor\FileUploadProcessor;

#[ApiResource(
    new Post('/common/open/upload', formats: ['multipart' => 'multipart/form-data'], inputFormats: ['multipart' => 'multipart/form-data'], processor: FileUploadProcessor::class,)
)]
class Common
{

}

以下是上传文件及返回信息

#src/State/Common/Processor/FileUploadProcessor.php
<?php

namespace App\State\Common\Processor;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use Symfony\Component\HttpFoundation\JsonResponse;

class FileUploadProcessor implements ProcessorInterface
{

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
    {
        $path = '';//上传文件的逻辑,这里不再详细写出来
        return new JsonResponse([
            'path' => $path
        ]);
    }
}

为了查找所有 API 资源类,API Platform 仅扫描两个目录来查找此属性: src/Entity/ 和 src/ApiResource 。不过,这可以在 /config/packages/api_platform.yaml 中使用映射路径配置进行调整。

注意,在api platform3.3以前文件上传使用Controller需要:

api_platform:
    use_symfony_listeners: true

从3.3开始,我们建议使用处理器,请注意,您需要全局启用多部分格式:

api_platform:
    use_symfony_listeners: false

在POST中的第一个参数:uriTemplate就是API路由端点,顺便提一句:你可能会觉得把所有代码写在Entity文件里太凌乱,其实你也可以 把ApiResource写在类似于Common这样的文件里,我们先修改一下Common文件,仅仅做为演示:

#[ApiResource(
    shortName: 'test',
    stateOptions: new Options(entityClass: User::class)
)]

然后刷新API文档,测试一下每一个端点。是不是很有意思?但是在我的开发经验中,这种方式我还并不是很熟练以及我并没觉得它比我们前面 的方式更好。如果你仔细研究后觉得这是更好的方式,请告诉我们。

接下来我们再为user自定义一个Get操作

#[ApiResource(
    operations: [
        new GetCollection(provider: UserProvider::class),
        new Get(provider: UserProvider::class),
        new Post(processor: UserProcessor::class),
        new Patch(processor: UserProcessor::class),
        new Delete(),
        new Get('/users/test',provider: UserTestProvider::class)
    ],
)]
<?php

namespace App\State\User\Provider;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use Symfony\Component\HttpFoundation\JsonResponse;

class UserTestProvider implements ProviderInterface
{

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        return new JsonResponse(['done']);
    }
}

当我们访问/api/users/test时,你可能会出现两种错误:

  1. "assert($user instanceof User)",
  2. 404

404很好理解,就是没有找到路由,

第一个错误我们通过控制台看到是/src/State/User/Provider/UserProvider.php的错误,我们去修改一下:

if ($user instanceof User){
            $user->setEmail('123@qq.com');
        }

修改后还是会返回404, 其实这是因为有一个默认的Get接口,路由为/api/users/{id},和我们这个接口有冲突,这是symfony的 原因,与Api platform无关,解决办法如下:

#[ApiResource(
    operations: [
        new GetCollection(provider: UserProvider::class),
        new Get(requirements: ['id' => '\d+'], provider: UserProvider::class),
        new Post(processor: UserProcessor::class),
        new Patch(processor: UserProcessor::class),
        new Delete(),
        new Get('/users/test', provider: UserTestProvider::class)
    ],
)]

注意:默认情况下,API Platform 使用定义的第一个 Get 操作来生成项目的 IRI,并使用第一个 GetCollection 操作来生成集合的 IRI。

所以,当你有多个Get或者GetCollection时,如果返回资源中的@id有问题,请先试一试修改顺序