selency/openapi

提供了一个用户友好的、面向对象的库,用于使用PHP构建OpenAPI规范。

v0.1.0 2023-03-23 15:06 UTC

This package is auto-updated.

Last update: 2024-09-09 10:19:04 UTC


README

OpenApi组件提供了一个用户友好的、面向对象的API,用于使用PHP构建OpenAPI规范。

赞助商

Selency 是一个由80人组成的团队,每个人都比前一个人更加致力于推广二手商品并为我们的地球做些积极的事情。开源软件与我们的价值观完美契合,因此我们通过一系列组件为Symfony生态系统做出贡献。

我们一直在寻找有才华的人,加入我们

使用方法

composer require selency/openapi

Selency OpenApi实现了OpenApi标准的3.1版本。它允许使用PHP对象构建文档,为OpenApi定义提供了更多的灵活性和可重用性。支持OpenApi 3.1的所有功能。

编写文档

OpenApi组件提供了面向对象的PHP工具来构建文档定义

// openapi/Documentation.php

use Selency\OpenApi\Documentation\AbstractDocumentation;

class Documentation extends AbstractDocumentation
{
    public function getIdentifier(): string
    {
        return 'myapi';
    }

    public function getVersion(): string
    {
        return '1.3.4';
    }

    public function configure(DocumentationConfigurator $doc): void
    {
        $doc->info($this->openApi->info()
            ->title('Monolith API')
            ->description(file_get_contents(__DIR__.'/Resources/info_description.md'))
            ->contact(name: 'API support', url: 'https://symfony.com.cn', email: 'contact@symfony.com')
            ->specificationExtension('x-logo', [
                'url' => 'https://symfony.com.cn/logos/symfony_black_02.png',
                'altText' => 'Symfony logo',
            ])
            ->license('MIT')
        );

        $doc->externalDocs(url: 'https://github.com/symfony/openapi', description: 'OpenApi component');

        $doc->server($this->openApi->server('https://api.symfony.local')->description('Local'))
            ->server($this->openApi->server('https://api.symfony-staging.com')->description('Staging'))
            ->server($this->openApi->server('https://api.symfony.com')->description('Prod'));

        $doc->securityRequirement(self::REF_SECURITY_USER_JWT);

        $doc->path('/health', $this->openApi->pathItem()
            ->get($this->openApi->operation()
                ->tag('Health')
                ->operationId('app.health.check')
                ->summary('Health check')
                ->description('Check the API is up and available.')
                ->securityRequirement(null)
                ->responses($this->openApi->responses()
                    ->response('200', $this->openApi->response()
                        ->description('When the API is up and available.')
                        ->content('application/json', $this->openApi->schema()
                            ->property('name', $this->openApi->schema()->type('string')->description('Name for this API')->example('Selency API'))
                            ->property('env', $this->openApi->schema()->type('string')->description('Current environment of this instance of the API')->example('prod'))
                        )
                    )
                    ->response('500', $this->openApi->response()->description('When the API is unavailable due to a backend problem.'))
                )
            )
        );

        // ...
    }
}

// Build a read-only model representing the documentation
$compiler = new DocumentationCompiler();
$openApiDefinition = $compiler->compile($doc);

// Compile it as YAML or JSON for usage in other tools
$openApiYaml = (new Dumper\YamlDumper())->dump($openApiDefinition);
$openApiJson = (new Dumper\JsonDumper())->dump($openApiDefinition);

将文档拆分为多个文件

该组件提供了一个部分文档的概念,允许将文档拆分为多个文件以提高可读性

// HealthDocumentation.php
use Selency\OpenApi\Documentation\PartialDocumentationInterface;

#[AutoconfigureTag('app.partial_documentation')]
class HealthDocumentation implements PartialDocumentationInterface
{
    public function __construct(private OpenApiBuilderInterface $openApi)
    {
    }

    public function configure(DocumentationConfigurator $doc): void
    {
        $doc->path('/health', $this->openApi->pathItem()
            ->get($this->openApi->operation()
                ->tag('Health')
                ->operationId('app.health.check')
                ->summary('Health check')
                ->description('Check the API is up and available. Mostly used by the infrastructure to check for readiness.')
                ->securityRequirement(null)
                ->responses($this->openApi->responses()
                    ->response('200', $this->openApi->response()
                        ->description('When the API is up and available.')
                        ->content('application/json', $this->openApi->schema()
                            ->property('name', $this->openApi->schema()->type('string')->description('Name for this API')->example('Selency API'))
                            ->property('env', $this->openApi->schema()->type('string')->description('Current environment of this instance of the API')->example('prod'))
                        )
                    )
                    ->response('500', $this->openApi->response()->description('When the API is unavailable due to a backend problem.'))
                )
            )
        );
    }
}


// Documentation.php
class Documentation extends AbstractDocumentation
{
    private iterable $partialsDocs;

    public function __construct(
        private Builder\OpenApiBuilder $openApi,
        #[TaggedIterator(tag: 'app.partial_documentation')] iterable $partialsDocs,
    ) {
        $this->partialsDocs = $partialsDocs;
    }

    public function getIdentifier(): string
    {
        return 'myapi';
    }

    public function getVersion(): string
    {
        return '1.3.4';
    }

    public function configure(DocumentationConfigurator $doc): void
    {
        $doc->info($this->openApi->info()
            ->title('Monolith API')
            ->description(file_get_contents(__DIR__.'/Resources/info_description.md'))
            ->contact(name: 'API support', url: 'https://symfony.com.cn', email: 'contact@symfony.com')
            ->specificationExtension('x-logo', [
                'url' => 'https://symfony.com.cn/logos/symfony_black_02.png',
                'altText' => 'Symfony logo',
            ])
            ->license('MIT')
        );

        // ...

        // Apply partial documentations
        foreach ($this->partialsDocs as $partialsDoc) {
            $partialsDoc->configure($doc);
        }
    }
}

将文档存储在您的应用程序代码附近

OpenApi组件提供了两个接口,有助于通过将文档存储在代码附近来维护文档

  • SelfDescribingSchemaInterface 可以由描述模式的类(请求、响应、有效负载等)实现;
  • SelfDescribingQueryParametersInterface 可以由描述查询参数列表的类实现;

这些接口在处理API的输入和输出时使用对象时特别有用

class AuthRegisterPayload implements SelfDescribingSchemaInterface
{
    #[Assert\Email(mode: Email::VALIDATION_MODE_STRICT)]
    #[Assert\NotBlank]
    public $email;

    #[Assert\Type(type: 'string')]
    #[Assert\NotBlank]
    public $firstName;

    #[Assert\Type(type: 'string')]
    #[Assert\NotBlank]
    public $lastName;

    #[Assert\Type(type: 'string')]
    #[Assert\NotBlank]
    public $password;

    public static function describeSchema(SchemaConfigurator $schema, OpenApiBuilderInterface $openApi): void
    {
        $schema
            ->title('AuthRegister')
            ->required(['email', 'firstName', 'lastName', 'password'])
            ->property('email', $openApi->schema()
                ->type('string')
                ->description('User\'s email')
                ->example('john.doe@domain.com')
            )
            ->property('firstName', $openApi->schema()
                ->type('string')
                ->description('User\'s first name')
                ->example('John')
            )
            ->property('lastName', $openApi->schema()
                ->type('string')
                ->description('User\'s last name')
                ->example('Doe')
            )
            ->property('password', $openApi->schema()
                ->type('string')
                ->description('User\'s plaintext password')
            )
        ;
    }
}

然后,您可以通过在编译期间提供专用加载器来加载这些自描述的架构/查询参数类

// Build a read-only model representing the documentation
$compiler = new DocumentationCompiler([
    new Selency\OpenApi\Loader\SelfDescribingSchemaLoader([
        AuthRegisterPayload::class,
    ])
]);

$openApiDefinition = $compiler->compile($doc);

注意:在Symfony应用程序中,SelfDescribingSchemaInterface和SelfDescribingQueryParametersInterface类会自动添加到编译器中,您不需要做任何事情。

并在您的定义中使用它们

class Documentation extends AbstractDocumentation
{
    // ...

    public function configure(DocumentationConfigurator $doc): void
    {
        // ...

        $doc->path('/auth/register', $this->openApi->pathItem()
            ->post($this->openApi->operation()
                ->tag('Auth')
                ->operationId('app.auth.register')
                ->summary('Auth registration')
                ->description('Register as a user.')
                ->securityRequirement(null)
                ->requestBody($this->openApi->requestBody()
                    ->content('application/json', AuthRegisterPayload::class)
                )
                ->responses($this->openApi->responses()
                    ->response('200', $this->openApi->response()
                        ->content('application/json', AuthRegisterOutput::class)
                    )
                )
            )
        );

        // ...
    }
}