arkitechdev / openapi
用于创建 OpenAPI 3 规范文件的包。
Requires
- php: ^7.3|^8.0
- symfony/yaml: ^5.1
README
需求
- PHP 7.3 | PHP 8.0
- Laravel 8(如果您想使用外观和命令的话)
安装
使用 composer 安装
composer require arkitechdev/openapi
您可以通过运行以下 artisan 命令生成文档:
php artisan openapi:generate
该命令会为您创建配置文件,但您也可以通过以下命令发布它:
php artisan vendor:publish
配置
配置文件看起来像这样
<?php return [ /** * The default path to the file containing the docs. */ 'docs_path' => base_path('docs.php'), /** * The path to the file where the generated documentation will be stored. * The generator supports both .json and .yaml files. */ 'output_path' => base_path('docs.json'), ];
docs_path
包含您的文档规范文件的路径。
output_path
是您希望存储生成的 API 文档的位置。如果文件名有 .json 扩展名,它将创建为一个 JSON 文件。如果文件名有 .yaml 扩展名,它将创建为一个 YAML 文件。很神奇。
入门
该库期望在项目的根目录中有一个名为 docs.php
的文件。您可以在配置文件中自定义此文件。要开始编写文档,您可以使用 OpenApi 外观。它的工作方式与 Laravel 中创建路由非常相似。以下是一个示例
docs.php
<?php use Arkitechdev\OpenApi\Facades\OpenApi; OpenApi::title('Sample API'); OpenApi::description('This is just an example of a docs file'); OpenApi::version('1.0.0'); OpenApi::addServer('/', 'localhost'); OpenApi::schemas([ Schemas\Post::class, Schemas\User::class, Schemas\Image::class, ]); OpenApi::get('/posts', \App\Docs\Requests\PostIndex::class); OpenApi::post('/posts', \App\Docs\Requests\PostStore::class); OpenApi::get('/posts/{id}', \App\Docs\Requests\PostShow::class); OpenApi::patch('/posts/{id}', \App\Docs\Requests\PostUpdate::class); OpenApi::delete('/posts/{id}', \App\Docs\Requests\PostDestroy::class); OpenApi::get('/users', \App\Docs\Requests\UserIndex::class); OpenApi::post('/users', \App\Docs\Requests\UserStore::class); OpenApi::get('/users/{id}', \App\Docs\Requests\UserShow::class); OpenApi::patch('/users/{id}', \App\Docs\Requests\UserUpdate::class); OpenApi::delete('/users/{id}', \App\Docs\Requests\UserDestroy::class);
然后,只需使用 artisan 命令生成 Open API 文档的 json 或 yaml 文件。简单!
有各种方法来构建您的文档。下面将进行解释。
方法
类中的大多数方法都相对直观,并提供自动补全功能。尽管如此,这里还有一些值得提到的事情。
获取器和设置器
许多方法要么设置属性值并返回自身,要么在没有设置或为 null 的参数的情况下返回属性值。
示例
OpenApi::title('A title')
这会将 title
属性设置为 A title
并返回 OpenApi 对象。这允许进行链式操作,例如
OpenApi::title('A title')->description('Whatever')->version('1.0.0')
非常方便。同时,如果没有设置参数,则返回值
$title = OpenApi::title() // $title = 'A title'
回调
一些方法提供了回调。当设置子对象(如参数或属性)时,该方法提供了一个可选的回调,用于进一步定义对象。根据您添加的类型,回调的第一个和唯一参数是添加的对象。
考虑以下示例
Request::method('get')->addParameter('page', null, function (Parameter $parameter) { $parameter->in(Parameter::IN_QUERY)->type(Parameter::TYPE_INTEGER)->required(true); });
由于我们添加了一个参数,所以回调的参数是一个 Parameter
对象。多么好的例子。在这里,我们添加了一个名为 'page' 的 query
参数,类型为 integer
并将其设置为 required
。
您可以用这种方式构建整个 API 文档,尽管我不建议这样做。请考虑以下相当冗长的示例。
OpenApi::title('API example') ->version('1.0.0') ->addPath('patch', '/posts/{id}', null, function (Request $request) { $request->tags([ 'Posts' ]) ->addParameter('id', null, function (Parameter $parameter) { $parameter->in(Parameter::IN_PATH) ->type(Parameter::TYPE_INTEGER) ->required(true); }) ->addContentType(Request::TYPE_JSON, null, function (Schema $schema) { $schema->type(Schema::TYPE_OBJECT) ->addProperty('title', null, function (Property $property) { $property->type(Property::TYPE_STRING) ->required(true); }) ->addProperty('subject', null, function (Property $property) { $property->type(Property::TYPE_STRING) ->format(Property::FORMAT_DATETIME); }) ->addProperty('content', null, function (Property $property) { $property->type(Property::TYPE_STRING); }); }) ->addResponse(200, function (Response $response) { $response->addContentType(Response::TYPE_JSON, null, function (Schema $schema) { $schema->type(Schema::TYPE_OBJECT) ->addProperty('title', null, function (Property $property) { $property->type(Property::TYPE_STRING); }) ->addProperty('subject', null, function (Property $property) { $property->type(Property::TYPE_STRING) ->format(Property::FORMAT_DATETIME); }) ->addProperty('content', null, function (Property $property) { $property->type(Property::TYPE_STRING); }); }); }); });
它很快就会变得难以管理,这就是为什么这个库支持将一切拆分为类。我将在下一章中介绍这一点。
方法默认值
如果您只想创建参数和属性而不需要所有这些杂七杂八的东西,那么我有您的后背。注意上面示例中的那些 null
参数?那就是类型所在的地方。
示例
$request->addParameter('title', Parameter::TYPE_STRING); $request->addParameter('page', Parameter::TYPE_INTEGER); // or if you hate constants for some reason: $request->addParameter('title', 'string'); $request->addParameter('page', 'integer');
实际上,string
是参数和属性的默认类型,所以对于该类型可以进一步简化
$request->addParameter('title'); $schema->addProperty('content');
您已经明白了。
其他类型有不同的默认值。请自由地深入研究代码。
基于类的 API 文档
现在,上面的大例子只是一个单个端点。想象一下有100个端点。这将使维护变得更加困难。没有人有那么多时间去处理这些!
因此,如果add...()
方法的第一个参数是一个类,那么你可以将里面的所有内容都隐藏起来。语法看起来是这样的
$property->addProperty(Property::class); $request->addContentType(Schema::class); $request->addParameter(Parameter::class); $request->addResponse(Response::class); $response->addContentType(Schema::class); $schema->addProperty(Property::class);
只要它扩展了上面列表中的相关类,你就可以创建和使用任何你想要的类。然后,你可以在该类内部使用类属性和继承的方法来定义它。
请求以这种方式工作得很好。在你的文档文件中,你可以这样做
docs.php
OpenApi::get('/projects', ProjectIndex::class);
自定义类可能看起来像这样
ProjectIndex.php
<?php namespace App\Requests; use App\Schemas\PaginationLinks as PaginationLinksSchema; use App\Schemas\PaginationMeta as PaginationMetaSchema; use App\Schemas\Project as ProjectSchema; use Arkitechdev\OpenApi\Parameter; use Arkitechdev\OpenApi\Property; use Arkitechdev\OpenApi\Request; use Arkitechdev\OpenApi\Response; use Arkitechdev\OpenApi\Schema; class ProjectIndex extends Request { protected string $method = 'get'; protected string $description = 'The description goes here'; protected string $summary = 'The summary goes here'; protected array $tags = [ 'Projects' ]; public function __construct() { $this->addParameter('searchQuery', null, function (Parameter $parameter) { $parameter->in(Parameter::IN_QUERY)->required(false); }); $this->addParameter('page', Parameter::TYPE_INTEGER, function (Parameter $parameter) { $parameter->in(Parameter::IN_QUERY)->required(false)->example(1)->default(1); }); $this->addParameter('per_page', Parameter::TYPE_INTEGER, function (Parameter $parameter) { $parameter->in(Parameter::IN_QUERY)->required(false)->example(15)->default(15); }); $this->addResponse(200, function (Response $response) { $response->description('Returns the list of projects') ->addContentType(Response::TYPE_JSON, null, function (Schema $schema) { $schema->addProperty('data', Property::TYPE_ARRAY, function (Property $property) { $property->ref(ProjectSchema::class); })->addProperty('links', null, function (Property $property) { $property->ref(PaginationLinksSchema::class); })->addProperty('meta', null, function (Property $property) { $property->ref(PaginationMetaSchema::class); }); }); }); } }
直接来自Laraville。在这个例子中,我们没有创建Parameter、Response或Property类,但你完全可以做得更多。你可以根据需要将其深入到任意层级。
尽管如此,我们确实创建了Schema类。这样我们就可以将它们用作参考(稍后会有更多介绍)。
我知道你很想知道,所以这就是Project
架构
Project.php
<?php namespace App\Schemas; use Arkitechdev\OpenApi\Property; use Arkitechdev\OpenApi\Schema; class Project extends Schema { protected string $type = 'object'; public function __construct() { $this->addProperty('id', Property::TYPE_INTEGER, function (Property $property) { $property->example(1)->required(true); }); $this->addProperty('name', null, function (Property $property) { $property->example('My cool project')->required(true); }); $this->addProperty('description', null, function (Property $property) { $property->example('Nice long description')->required(true); }); $this->addProperty('created', null, function (Property $property) { $property->format(Property::FORMAT_DATETIME); }); } }
注意:如果你需要覆盖自定义类中的某些内容,仍然可以使用回调。非常好!
参考文献
如果你计划在属性中使用架构的引用($property->ref(Project::class)
),你必须确保将它们添加到OpenApi对象的架构数组中
docs.php
OpenApi::schemas([ Project::class, ]);
验证器
有一个带有内置验证器的在线编辑器,你可以使用它。
当你生成了你的JSON/YAML文件后,你可以将其内容粘贴到编辑器中,并检查它看起来是否正常。
路线图
我想要用这个包做的事情(不分先后顺序)
- 添加更多OpenApi功能
- 创建特性和Laravel模型、控制器等的集成(以使文档更接近代码)