自动化你的 OpenAPI 文档
发布时间:作者: 史蒂夫·麦克道格
多年来,作为开发者,我们一直在寻找自动化文档的方法,从 PHPDoc 到 Swagger 等等。近年来,API 世界发生了重大转变,开始采用以设计为主导的 API 文档方法。这主要得益于 OpenAPI 规范的创建,它是 Swagger 的替代方案。
作为开发者,我们往往没有时间或兴趣深入研究 API 的设计优先方法 - 事实就是这样。虽然这可能是最佳实践,也是每个人都在说的话。但现实情况是,我们被付钱来构建和发布功能,因此我们经常跳过一些步骤。如果我说你可以将一个小的步骤添加到你的工作流程中,让你可以边走边构建 API 文档,你会相信吗?
让我们开始吧。在本教程中,我将构建一个完全虚构的 API,没有目的,它也不是我通常构建 API 的方式。这样做是有原因的,因为我希望你专注于我采取的步骤。
我不会介绍初始设置步骤,因为之前已经介绍过很多次。在本教程中,我将做出很多假设 - 主要围绕你是否知道如何设置和安装 Laravel,以及如何使用 artisan 生成类。
我们在 IDE 中打开了一个 Laravel 项目,我们需要添加一些功能。我发现,在构建 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(); });}
这在一定程度上反映了 Google Books API。我们有一个书籍标题和副标题,即书籍的名称。描述和语言说明了书籍的主题和使用语言。我们有一个页数来查看它是否是一本厚重的书。然后我们有作者、类别、图片和 ISBN 来添加更多信息。最后,还有出版日期。
现在我们有了模型和数据库,我们可以开始研究 API 本身。我们的第一步是安装一个包,让我们能够生成 API 文档。有一个很棒的包叫 Scribe 你可以用它,它在 Laravel 和简单的 PHP 应用程序中都有效。你可以使用以下 composer 命令来安装它
composer require --dev knuckleswtf/scribe
安装完成后,你可以按照设置说明在你的应用程序中使其正常工作。安装完成后并发布配置后,你可以对 API 文档生成进行测试运行,以确保其按预期工作。你应该能够在开箱即用时获得类似这样的结果
openapi: 3.0.3info: title: Laravel description: '' version: 1.0.0servers: - 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 规范中,这比没有好。但是,通过一些额外的努力,我们可以记录参数并将事物分组,从而使 API 文档更有意义。在 Scribe 中,你可以使用 DocBlocks 或属性来做到这一点。我自己更喜欢使用 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 文档。然后我们添加 "Get all Books from the API.",它描述了特定的端点。然后我们可以添加额外的 @queryParam
条目来记录这个端点接受的可用查询参数。这比编写 YAML 方便多了,对吧!
Scribe 文档提供了更多信息,你可以深入了解添加的信息。我在这里只介绍了基础知识 - 但你已经可以看到它有多有用。你使用什么工具来编写 API 文档?在 Twitter 上告诉我们吧!