在Laravel项目中自动化你的OpenAPI文档

2023-06-01 00:00:00 文档 项目 自动化

多年来,作为开发者,我们一直在寻找能使我们的文档自动化的方法,从PHPDoc到Swagger以及其他。近年来,在API世界中出现了一个重大的转变,即采用更加以设计为先的方法来编写API文档。

这主要是由替代Swagger的OpenAPI规范的建立所刺激的。


作为开发者,我们通常没有时间或倾向于对我们的API采用设计优先的方法,这只是一个事实。

虽然这可能是最佳实践,也是大家都在说的。但在现实中,我们的工资是用来构建和交付功能的,所以我们经常跳过一些步骤。如果我告诉你,你可以在你的工作流程中增加一个小步骤,让你在工作中建立API文档,你会怎样?


让我们开始吧。在本教程中,我将建立一个完全虚构的API,没有任何目的,而且也不是我通常会建立一个API的方式。这是有原因的,因为我希望你能纯粹地关注我所采取的步骤。

我不会去看最初的设置步骤,因为这在过去已经有很多了。

在这个教程中,我将做出许多假设--主要是围绕你知道如何设置和安装Laravel,但也包括使用artisan来生成类。


我们在IDE中打开了一个Laravel项目, 我们需要添加一些功能. 我发现在构建API时,评估API的目的是有帮助的。试着去了解为什么要建立它,它是为了什么而建立的。这可以让我们了解什么是对我们的API有用的,并阻止我们添加更多根本不需要的端点。


在本教程中,我们将构建一个简单的书架式API。它将允许我们创建一些相当基本的功能--这样我们就有了建立的目的。

这个API的目标是消费者,他们可以连接到我们的API来管理他们的书。

对这些用户来说,最重要的是能看到他们的书籍并快速添加书籍的方法。

最终会有其他的功能,但总是以每个用户需要的主要目的来开始一个API。


为Book模型创建一个新的模型、迁移和工厂。这将是我们API中的主要模型。

在这个模型上你想要的属性对本教程来说并不重要--但我还是要讲一下。

public function up(): void
{
    Schema::create('books', static function (Blueprint $table): void {
        $table->id();
 
        $table->string('title');
        $table->string('subtitle')->nullable();
        $table->text('description');
        $table->string('language');
 
        $table->unsignedBigInteger('pages');
 
        $table->json('authors');
        $table->json('categories');
        $table->json('images');
        $table->json('isbn');
 
        $table->date('published_at');
        $table->timestamps();
    });
}

这在一定程度上反映了谷歌图书的API。我们有一个书名和副标题,这是该书的名称。

描述和语言解释了这本书是关于什么的,用什么语言。

我们有一个页数,看它是否是一本大书。

然后我们有作者、类别、图像和ISBN,以增加额外的信息。最后,还有出版日期。


现在我们有了模型和数据库,我们可以开始研究API本身。我们的第一步将是安装一个包,让我们生成我们的API文档。

有一个很好的包叫Scribe,你可以使用,它在Laravel和简单的PHP应用程序中都可以使用。

你可以用下面的composer命令来安装它:

composer require --dev knuckleswtf/scribe

一旦你安装了这个,你可以按照设置说明让它在你的应用程序中工作。

一旦安装好了,配置也发布了,你就可以对API文档的生成进行测试,以确保它按预期工作。

你应该得到像这样的开箱即用的东西。

openapi: 3.0.3
info:
  title: Laravel
  description: ''
  version: 1.0.0
servers:
  -
    url: 'http://localhost:8000'
paths: []
tags: []

现在我们知道它在工作,我们可以考虑添加一些路由,以确保它们在OpenAPI生成中被使用。

让我们从我们的第一个路由开始,围绕着获取图书、获取图书列表和获取单一图书。

Route::prefix('books')->as('books:')->middleware(['api'])->group(static function (): void {
    Route::get(
        '/',
        App\Http\Controllers\Api\Books\IndexController::class,
    )->name(
        name: 'index',
    );
 
    Route::get(
        '{book}',
        App\Http\Controllers\Api\Books\ShowController::class,
    )->name(
        name: 'show',
    );
});

我们在books的前缀下有两条路由,都是不需要认证的GET路由。

这些将是公共API的一部分,任何人都可以访问。


现在我们已经有了路由和控制器。我们需要考虑如何处理这些路由。我们希望对我们的请求进行分类和过滤,这样我们就可以请求特定的书籍列表。

为了实现这一点, 我们将使用Spatie Laravel Query Builder包, 提供一个简洁的界面来搜索和过滤我们需要的东西. 让我们使用下面的composer命令来安装它:

composer require spatie/laravel-query-builder

一旦安装完毕,我们就可以考虑如何过滤我们的API请求,以获得正确的书籍列表。所有好的API,都有分页功能。

为了实现这一点, 我们可以使用Laravel的内置分页器. 然而, Aaron Francis创建了一个名为Fast Paginate的分页器,

它的性能要好得多--当它涉及到API的时候,这一点很重要。

你可以使用下面的 composer 命令来安装它:

composer require hammerstone/fast-paginate

让我们把这两件事结合起来,这样我们就可以建立一个集合并返回一个分页的结果。

return QueryBuilder::for(
    subject: Book::class,
)->allowedFilters(
    filters: ['language', 'pages', 'published_at'],
)->fastPaginate();

这对我们的用例很有效--然而,我并不喜欢直接从你的API返回查询结果。

我们应该总是使用API资源将响应转换为可控的格式。

Laravel有内置的资源, 或者你可以使用PHP Leagues Fractal包, 这是很好的。

在这个例子中, 我将使用一个我以前写过的包, 所以我就不详细介绍了. 

最终, 我们应该得到一个看起来像下面的控制器, 或者至少与它很相似:

final class IndexController
{
    public function __invoke(Request $request): JsonResponse
    {
        return new JsonResponse(
            data: BookResource::collection(
                resource: QueryBuilder::for(
                    subject: Book::class,
                )->allowedFilters(
                    filters: ['language', 'pages', 'published_at'],
                )->fastPaginate(),
            ),
        );
    }
}

到目前为止,这只能在OpenAPI规范中注册我们的路由,这总比没有好。

但是,通过一些额外的工作,我们可以记录参数和分组,使之更有意义。在Scribe中,你可以使用DocBlocks或Attributes来做这件事。

我自己更喜欢用DocBlocks来做这个,因为你想使用的所有字段都没有属性。

让我给你看一个例子,我将带你了解我所做的一切。

final class IndexController
{
    /**
     * @group Book Collection
     *
     * Get all Books from the API.
     *
     * @queryParam filter[language] Filter the books to a specific language. filter[language]=en
     * @queryParam filter[pages] Filter the books to those with a certain amount of pages. filter[pages]=1000
     * @queryParam filter[published_at] Filter the books to those published on a certain date. filter[published_at]=12-12-1992
     *
     */
    public function __invoke(Request $request): JsonResponse
    {
        return new JsonResponse(
            data: BookResource::collection(
                resource: QueryBuilder::for(
                    subject: Book::class,
                )->allowedFilters(
                    filters: ['language', 'pages', 'published_at'],
                )->fastPaginate(),
            ),
        );
    }
}

我们首先添加@group Book Collection,这将把这个端点归入 "Book Collection",以便更容易浏览你的API文档。然后,我们添加 "从API获取所有书籍",描述这个特定的端点。然后我们可以添加额外的@queryParam条目来记录这个端点的可用查询参数。这比写YAML要容易得多,对吗?

Scribe文档有更多的信息,而且你可以深入了解你所添加的信息。

我在这里只介绍了基本情况--但你已经可以看到这有多有用了。

你在API文档中使用什么?请在Twitter上告诉我们。


转:

https://laravel-news.com/automating-your-openapi-documentation

相关文章