Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example schema-query-parameter-attributes processor #1569

Merged
merged 4 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Examples/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ class MyCustomProcessor

[source](processors/schema-query-parameter)

* **schema-query-parameter-attributes processor**

Same as the `schema-query-parameter` processor but uses php attributes instead of annotations.
A processor that takes a vendor tag (expecting a schema `#ref`) and injects all properties of that given schema as
query parameter to the [request definition](processors/schema-query-parameter-attributes/SchemaQueryParameter.php).

[source](processors/schema-query-parameter-attributes)

* **sort-components processor**

A processor that sorts components so they appear in alphabetical order.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace SchemaQueryParameterProcessor;

use OpenApi\Analysis;
use OpenApi\Annotations\Operation;
use OpenApi\Generator;
use OpenApi\Attributes\Parameter;
use OpenApi\Attributes\Schema;

/**
* Custom processor to translate the vendor tag `query-args-$ref` into query parameter annotations.
*
* Details for the parameters are taken from the referenced schema.
*/
class SchemaQueryParameter
{
public const REF = 'query-args-$ref';

public function __invoke(Analysis $analysis): void
{
/** @var Operation[] $operations */
$operations = $analysis->getAnnotationsOfType(Operation::class);

foreach ($operations as $operation) {
if ($operation->x !== Generator::UNDEFINED && array_key_exists(self::REF, $operation->x)) {
if (!is_string($operation->x[self::REF])) {
throw new \InvalidArgumentException('Value of `x.' . self::REF . '` must be a string');
}

$schema = $analysis->getSchemaForSource($operation->x[self::REF]);
if (!$schema instanceof Schema) {
throw new \InvalidArgumentException('Value of `x.' . self::REF . "` contains reference to unknown schema: `{$operation->x[self::REF]}`");
}

$this->expandQueryArgs($operation, $schema);
$this->cleanUp($operation);
}
}
}

/**
* Expand the given operation by injecting parameters for all properties of the given schema.
*/
private function expandQueryArgs(Operation $operation, Schema $schema): void
{
if ($schema->properties === Generator::UNDEFINED || !$schema->properties) {
return;
}

$operation->parameters = $operation->parameters === Generator::UNDEFINED ? [] : $operation->parameters;

foreach ($schema->properties as $property) {
$isNullable = $property->nullable !== Generator::UNDEFINED ? $property->nullable : false;
$schema = new Schema(
type: $property->format !== Generator::UNDEFINED ? $property->format : $property->type,
nullable: $isNullable
);
$schema->_context = $operation->_context; // inherit context from operation, required to pretend to be a parameter

$parameter = new Parameter(
name: $property->property,
description: $property->description !== Generator::UNDEFINED ? $property->description : null,
in: 'query',
required: !$isNullable,
schema: $schema,
example: $property->example,
);
$parameter->_context = $operation->_context; // inherit context from operation, required to pretend to be a parameter

$operation->parameters[] = $parameter;
}
}

private function cleanUp(Operation $operation): void
{
unset($operation->x[self::REF]);
if (!$operation->x) {
$operation->x = Generator::UNDEFINED;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App;

use OpenApi\Attributes as OA;

/**
* Uses a custom processor `QueryArgsFromSchema` processor to convert a vendor extension into query parameters.
*
* The parameters are extracted from the schema referenced by the custom extension.
*/
#[OA\OpenApi(
info: new OA\Info(version: '1.0.0', title: 'Example of using a custom processor in swagger-php'),
)]
class OpenApi
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App;

use OpenApi\Attributes as OA;

#[OA\Schema(
title: 'Product',
description: 'A simple product model',
)]
class Product
{
public function __construct(
#[OA\Property(
title: 'The unique identifier of a product in our catalog.',
example: 43,
)]
public int $id,
#[OA\Property(
title: 'The name of the product.',
example: 'Lorem ipsum',
)]
public string|null $name,
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace App;

use OpenApi\Attributes as OA;
use OpenApi\Attributes\Response;
use SchemaQueryParameterProcessor\SchemaQueryParameter;

class ProductController
{
#[OA\Get(
path: '/products/{id}',
tags: ['Products'],
parameters: [
new OA\PathParameter(
name: 'id',
required: true,
),
],
responses: [
new Response(
response: 200,
description: 'A single product',
content: new OA\JsonContent(
ref: Product::class
)
),
],
)]
public function getProduct($id)
{
}

#[OA\Get(
path: '/products/search',
tags: ['Products'],
responses: [
new Response(
response: 200,
description: 'A single product',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(ref: Product::class)
)
),
],
x: [SchemaQueryParameter::REF => Product::class],
)]
public function findProducts($id)
{
}
}
29 changes: 29 additions & 0 deletions Examples/processors/schema-query-parameter-attributes/scan.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

use OpenApi\Generator;
use OpenApi\Processors\BuildPaths;
use SchemaQueryParameterProcessor\SchemaQueryParameter;

$classLoader = require __DIR__ . '/../../../vendor/autoload.php';

// register our app namespace...
$classLoader->addPsr4('App\\', __DIR__ . '/app');
// and our custom processor
$classLoader->addPsr4('SchemaQueryParameterProcessor\\', __DIR__);

$openapiGenerator = new Generator();
$processors = [];
foreach ($openapiGenerator->getProcessors() as $processor) {
$processors[] = $processor;
if ($processor instanceof BuildPaths) {
$processors[] = new SchemaQueryParameter();
}
}

$openapi = $openapiGenerator
->setProcessors($processors)
->generate([__DIR__ . '/app']);

file_put_contents(__DIR__ . '/schema-query-parameter.yaml', $openapi->toYaml());

echo $openapi->toYaml();
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
openapi: 3.0.0
info:
title: 'Example of using a custom processor in swagger-php'
version: 1.0.0
paths:
'/products/{id}':
get:
tags:
- Products
operationId: 399b71a7672f0a46be1b5f4c120c355d
parameters:
-
name: id
in: path
required: true
responses:
'200':
description: 'A single product'
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
/products/search:
get:
tags:
- Products
operationId: 178f74de3417eec20dee95709821e6ca
parameters:
-
name: id
in: query
required: true
schema:
type: integer
nullable: false
example: 43
-
name: name
in: query
required: false
schema:
type: string
nullable: true
example: 'Lorem ipsum'
responses:
'200':
description: 'A single product'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Product'
components:
schemas:
Product:
title: Product
description: 'A simple product model'
properties:
id:
title: 'The unique identifier of a product in our catalog.'
type: integer
example: 43
name:
title: 'The name of the product.'
type: string
example: 'Lorem ipsum'
nullable: true
type: object
20 changes: 20 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
parameters:
ignoreErrors:
-
message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#"
count: 1
path: Examples/processors/schema-query-parameter-attributes/SchemaQueryParameter.php

-
message: "#^Property OpenApi\\\\Annotations\\\\AbstractAnnotation\\:\\:\\$x \\(array\\<string, mixed\\>\\) does not accept string\\.$#"
count: 1
path: Examples/processors/schema-query-parameter-attributes/SchemaQueryParameter.php

-
message: "#^Strict comparison using \\=\\=\\= between array\\<OpenApi\\\\Annotations\\\\Parameter\\> and '@OA\\\\\\\\Generator\\:…' will always evaluate to false\\.$#"
count: 1
path: Examples/processors/schema-query-parameter-attributes/SchemaQueryParameter.php

-
message: "#^Strict comparison using \\=\\=\\= between array\\<OpenApi\\\\Annotations\\\\Property\\> and '@OA\\\\\\\\Generator\\:…' will always evaluate to false\\.$#"
count: 1
path: Examples/processors/schema-query-parameter-attributes/SchemaQueryParameter.php

-
message: "#^Result of && is always true\\.$#"
count: 1
Expand Down