Add Generator class for OO use (#928)

Also starts work on making `src/functions.php` obsolete.

* `Generator` class wraps all config/settings in a single place
* `UNDEFINED` const migrated to `Generator::UNDEFINED`
* PHP token code refactored to avoid having to use custom polyfill
* Migrate all code to use `Generator`
* Extend documentation on how to use swagger-php from PHP (using the
  `Generator` class)
* Deprecate all static API elements - `scan()` function,
  `Analyser::$whitelist` and `Analyser::$defaultImports`
This commit is contained in:
Martin Rademacher 2021-05-20 12:45:34 +12:00 committed by GitHub
parent c60c408edf
commit c5f0819764
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 1016 additions and 427 deletions

View File

@ -17,7 +17,9 @@ $options = [
'processors' => $processors,
];
$openapi = OpenApi\scan(__DIR__ . '/app', $options);
$openapi = (new OpenApi\Generator())
->setProcessors($processors)
->generate([__DIR__ . '/app']);
$spec = json_encode($openapi, JSON_PRETTY_PRINT);
file_put_contents(__DIR__ . '/schema-query-parameter-processor.json', $spec);
//echo $spec;

View File

@ -30,6 +30,7 @@
"tags": [
"Products"
],
"summary": "Controller that takes all `Product` properties as query parameter.",
"operationId": "App\\ProductController::findProducts",
"parameters": [
{

View File

@ -54,14 +54,15 @@ Generate always-up-to-date documentation.
```php
<?php
require("vendor/autoload.php");
$openapi = \OpenApi\scan('/path/to/project');
$openapi = \OpenApi\Generator::scan(['/path/to/project']);
header('Content-Type: application/x-yaml');
echo $openapi->toYaml();
```
Documentation of how to use the `Generator` class can be found in the [Generator Migration](https://zircote.github.io/swagger-php/Generator-migration.html) guide.
### Usage from the Command Line Interface
Generate the documentation to a static json file.
The `openapi` command line interface can be used to generate the documentation to a static yaml/json file.
```bash
./vendor/bin/openapi --help

View File

@ -230,7 +230,7 @@ foreach ($options["processor"] as $processor) {
Analysis::registerProcessor($processor);
}
$openapi = OpenApi\scan($paths, ['exclude' => $exclude, 'pattern' => $pattern]);
$openapi = OpenApi\Generator::scan(OpenApi\Util::finder($paths, $exclude, $pattern));
if ($exit !== 0) {
error_log('');

164
docs/Generator-migration.md Normal file
View File

@ -0,0 +1,164 @@
# Using the `Generator`
## Motivation
Code to perform a fully customized scan using `swagger-php` so far required to use 3 separate static elements:
1. `\OpenApi\scan()`
The function to scan for OpenApi annotations.
1. `Analyser::$whitelist`
List of namespaces that should be detected by the doctrine annotation parser.
1. `Analyser::$defaultImports`
Imports to be set on the used doctrine `DocParser`.
Allows to pre-define annotation namespaces. The `@OA` namespace, for example, is configured
as `['oa' => 'OpenApi\\Annotations']`.
The new `Generator` class provides an object-oriented way to use `swagger-php` and all its aspects in a single place.
## The `\OpenApi\scan()` function
For a long time the `\OpenApi\scan()` function was the main entry point into using swagger-php from PHP code.
```php
/**
* Scan the filesystem for OpenAPI annotations and build openapi-documentation.
*
* @param array|Finder|string $directory The directory(s) or filename(s)
* @param array $options
* exclude: string|array $exclude The directory(s) or filename(s) to exclude (as absolute or relative paths)
* pattern: string $pattern File pattern(s) to scan (default: *.php)
* analyser: defaults to StaticAnalyser
* analysis: defaults to a new Analysis
* processors: defaults to the registered processors in Analysis
*
* @return OpenApi
*/
function scan($directory, $options = []) { /* ... */ }
```
Using it looked typically something like this:
```php
require("vendor/autoload.php");
$openapi = \OpenApi\scan(__DIR__, ['exclude' => ['tests'], 'pattern' => '*.php']);
```
The two configuration options for the underlying Doctrine doc-block parser `Analyser::$whitelist` and `Analyser::$defaultImports`
are not part of this function and need to be set separately.
Being static this means setting them back is the callers responsibility and there is also the fact that
some of the Doctrine configuration currently can not be reverted easily.
Therefore, having a single side-effect free way of using swwagger-php seemed like a good idea...
## The `\OpenApi\Generator` class
The new `Generator` class can be used in object-oriented (and fluent) style which allows for easy customization
if needed.
In that case to actually process the given input files the **non-static** method `generate()` is to be used.
Full example of using the `Generator` class to generate OpenApi specs.
```php
require("vendor/autoload.php");
$validate = true;
$logger = new \Psr\Log\NullLogger();
$processors = [/* my processors */];
$finder = \Symfony\Component\Finder\Finder::create()->files()->name('*.php')->in(__DIR__);
$openapi = (new \OpenApi\Generator($logger))
->setProcessors($processors)
->setAliases(['MY' => 'My\Annotations'])
->setAnalyser(new \OpenApi\StaticAnalyser())
->setNamespaces(['My\\Annotations\\'])
->generate(['/path1/to/project', $finder], new \OpenApi\Analysis(), $validate);
```
The `aliases` property corresponds to the now also deprecated static `Analyser::$defaultImports`,
`namespaces` to `Analysis::$whitelist`.
Advantages:
* The `Generator` code will handle configuring things as before in a single place
* Static settings will be reverted to defaults once finished
* The get/set methods allow for using type hints
* Static configuration is deprecated and can be removed at some point without code changes
* Build in support for PSR logger
* Support for [Symfony Finder](https://symfony.com/doc/current/components/finder.html), `\SplInfo` and file/directory names (`string) as source.
The minimum code required, using the `generate()` method, looks quite similar to the old `scan()` code:
```php
/**
* Generate OpenAPI spec by scanning the given source files.
*
* @param iterable $sources PHP source files to scan.
* Supported sources:
* * string - file / directory name
* * \SplFileInfo
* * \Symfony\Component\Finder\Finder
* @param null|Analysis $analysis custom analysis instance
* @param bool $validate flag to enable/disable validation of the returned spec
*/
public function generate(iterable $sources, ?Analysis $analysis = null, bool $validate = true): \OpenApi\OpenApi { /* ... */ }
```
```php
require("vendor/autoload.php");
$openapi = (new \OpenApi\Generator())->generate(['/path1/to/project']);
```
For those that want to type even less and keep using a plain array to configure `swagger-php` there is also a static version:
```php
<?php
require("vendor/autoload.php");
$openapi = \OpenApi\Generator::scan(['/path/to/project']);
header('Content-Type: application/x-yaml');
echo $openapi->toYaml();
```
**Note:** While using the same name as the old `scan()` function, the `Generator::scan` method is not
100% backwards compatible.
```php
/**
* Static wrapper around `Generator::generate()`.
*
* @param iterable $sources PHP source files to scan.
* Supported sources:
* * string
* * \SplFileInfo
* * \Symfony\Component\Finder\Finder
* @param array $options
* aliases: null|array Defaults to `Analyser::$defaultImports`.
* namespaces: null|array Defaults to `Analyser::$whitelist`.
* analyser: null|StaticAnalyser Defaults to a new `StaticAnalyser`.
* analysis: null|Analysis Defaults to a new `Analysis`.
* processors: null|array Defaults to `Analysis::processors()`.
* validate: bool Defaults to `true`.
* logger: null|\Psr\Log\LoggerInterface If not set logging will use \OpenApi\Logger as before.
*/
public static function scan(iterable $sources, array $options = []): OpenApi { /* ... */ }
```
Most notably the `exclude` and `pattern` keys are no longer supported. Instead, a Symfony `Finder` instance can be passed in
as source directly (same as with `Generator::generate()`).
If needed, the `\OpenApi\Util` class provides a builder method that allows to keep the status-quo
```php
$exclude = ['tests'];
$pattern = '*.php';
$openapi = \OpenApi\Generator::scan(\OpenApi\Util::finder(__DIR__, $exclude, $pattern));
// same as
$openapi = \OpenApi\scan(__DIR__, ['exclude' => $exclude, 'pattern' => $pattern]);

View File

@ -12,20 +12,24 @@ composer require zircote/swagger-php
Generate always-up-to-date documentation.
```php{3-5}
```php
<?php
require("vendor/autoload.php");
$openapi = \OpenApi\scan('/path/to/project');
$openapi = \OpenApi\Generator::scan(['/path/to/project']);
header('Content-Type: application/x-yaml');
echo $openapi->toYaml();
```
This will scan the php-files in the given folder(s), look for OpenApi annotations and output a json file.
Complete documentation of how to use the `Generator` class can be found in the [Generator Migration](https://zircote.github.io/swagger-php/Generator-migration.html) guide.
## CLI
Instead of generating the documentation dynamically we also provide a command line interface.
This writes the documentation to a static json file.
This allows to write the documentation to a static yaml/json file.
```bash
./vendor/bin/openapi --help

View File

@ -23,7 +23,7 @@ Create a php file:
```php
<?php
require("vendor/autoload.php");
$openapi = \OpenApi\scan('/path/to/project');
$openapi = \OpenApi\Generator::scan(['/path/to/project']);
header('Content-Type: application/x-yaml');
echo $openapi->toYaml();
```

View File

@ -46,11 +46,15 @@ class Analyser
* Set to false to load all detected classes.
*
* @var array|false
*
* @deprecated use \OpenApi\Generator::setAliases() instead
*/
public static $whitelist = ['OpenApi\\Annotations\\'];
/**
* Use @OA\* for OpenAPI annotations (unless overwritten by a use statement).
*
* @deprecated use \OpenApi\Generator::setNamespaces() instead
*/
public static $defaultImports = ['oa' => 'OpenApi\\Annotations'];

View File

@ -8,6 +8,7 @@ namespace OpenApi\Annotations;
use OpenApi\Analyser;
use OpenApi\Context;
use OpenApi\Generator;
use OpenApi\Logger;
use Symfony\Component\Yaml\Yaml;
@ -23,7 +24,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
*
* @var array
*/
public $x = UNDEFINED;
public $x = Generator::UNDEFINED;
/**
* @var Context
@ -164,12 +165,12 @@ abstract class AbstractAnnotation implements \JsonSerializable
$property = $details->value;
if (is_array($property)) {
$property = $property[0];
if ($this->$property === UNDEFINED) {
if ($this->$property === Generator::UNDEFINED) {
$this->$property = [];
}
$this->$property[] = $this->nested($annotation, $nestedContext);
$mapped = true;
} elseif ($this->$property === UNDEFINED) {
} elseif ($this->$property === Generator::UNDEFINED) {
// ignore duplicate nested if only one expected
$this->$property = $this->nested($annotation, $nestedContext);
$mapped = true;
@ -253,7 +254,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
{
$properties = [];
foreach (get_object_vars($this) as $property => $value) {
if ($value !== UNDEFINED) {
if ($value !== Generator::UNDEFINED) {
$properties[$property] = $value;
}
}
@ -270,7 +271,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
// Strip undefined values.
foreach (get_object_vars($this) as $property => $value) {
if ($value !== UNDEFINED) {
if ($value !== Generator::UNDEFINED) {
$data->$property = $value;
}
}
@ -302,7 +303,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
continue;
}
$property = $nested[0];
if ($this->$property === UNDEFINED) {
if ($this->$property === Generator::UNDEFINED) {
continue;
}
$keyField = $nested[1];
@ -312,7 +313,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
$object->$key = $item;
} else {
$key = $item->$keyField;
if ($key !== UNDEFINED && empty($object->$key)) {
if ($key !== Generator::UNDEFINED && empty($object->$key)) {
if ($item instanceof \JsonSerializable) {
$object->$key = $item->jsonSerialize();
} else {
@ -378,7 +379,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
continue;
}
$property = $nested[0];
if ($this->$property === UNDEFINED) {
if ($this->$property === Generator::UNDEFINED) {
continue;
}
$keys = [];
@ -387,7 +388,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
if (is_array($item) && is_numeric($key) === false) {
Logger::notice($this->identity().'->'.$property.' is an object literal, use nested '.Logger::shorten($annotationClass).'() annotation(s) in '.$this->_context);
$keys[$key] = $item;
} elseif ($item->$keyField === UNDEFINED) {
} elseif ($item->$keyField === Generator::UNDEFINED) {
Logger::warning($item->identity().' is missing key-field: "'.$keyField.'" in '.$item->_context);
} elseif (isset($keys[$item->$keyField])) {
Logger::warning('Multiple '.$item->_identity([]).' with the same '.$keyField.'="'.$item->$keyField."\":\n ".$item->_context."\n ".$keys[$item->$keyField]->_context);
@ -396,7 +397,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
}
}
}
if (property_exists($this, 'ref') && $this->ref !== UNDEFINED && $this->ref !== null) {
if (property_exists($this, 'ref') && $this->ref !== Generator::UNDEFINED && $this->ref !== null) {
if (substr($this->ref, 0, 2) === '#/' && count($parents) > 0 && $parents[0] instanceof OpenApi) {
// Internal reference
try {
@ -408,7 +409,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
} else {
// Report missing required fields (when not a $ref)
foreach (static::$_required as $property) {
if ($this->$property === UNDEFINED) {
if ($this->$property === Generator::UNDEFINED) {
$message = 'Missing required field "'.$property.'" for '.$this->identity().' in '.$this->_context;
foreach (static::$_nested as $class => $nested) {
$nestedProperty = is_array($nested) ? $nested[0] : $nested;
@ -431,7 +432,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
// Report invalid types
foreach (static::$_types as $property => $type) {
$value = $this->$property;
if ($value === UNDEFINED || $value === null) {
if ($value === Generator::UNDEFINED || $value === null) {
continue;
}
if (is_string($type)) {
@ -502,7 +503,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
$properties = [];
foreach (static::$_parents as $parent) {
foreach ($parent::$_nested as $annotationClass => $entry) {
if ($annotationClass === $class && is_array($entry) && $this->{$entry[1]} !== UNDEFINED) {
if ($annotationClass === $class && is_array($entry) && $this->{$entry[1]} !== Generator::UNDEFINED) {
$properties[] = $entry[1];
break 2;
}
@ -544,7 +545,7 @@ abstract class AbstractAnnotation implements \JsonSerializable
$fields = [];
foreach ($properties as $property) {
$value = $this->$property;
if ($value !== null && $value !== UNDEFINED) {
if ($value !== null && $value !== Generator::UNDEFINED) {
$fields[] = $property.'='.(is_string($value) ? '"'.$value.'"' : $value);
}
}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A Components Object: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#components-object
@ -27,63 +29,63 @@ class Components extends AbstractAnnotation
*
* @var Schema[]
*/
public $schemas = UNDEFINED;
public $schemas = Generator::UNDEFINED;
/**
* Reusable Responses.
*
* @var Response[]
*/
public $responses = UNDEFINED;
public $responses = Generator::UNDEFINED;
/**
* Reusable Parameters.
*
* @var Parameter[]
*/
public $parameters = UNDEFINED;
public $parameters = Generator::UNDEFINED;
/**
* Reusable Examples.
*
* @var Examples[]
*/
public $examples = UNDEFINED;
public $examples = Generator::UNDEFINED;
/**
* Reusable Request Bodys.
*
* @var RequestBody[]
*/
public $requestBodies = UNDEFINED;
public $requestBodies = Generator::UNDEFINED;
/**
* Reusable Headers.
*
* @var Header[]
*/
public $headers = UNDEFINED;
public $headers = Generator::UNDEFINED;
/**
* Reusable Security Schemes.
*
* @var SecurityScheme[]
*/
public $securitySchemes = UNDEFINED;
public $securitySchemes = Generator::UNDEFINED;
/**
* Reusable Links.
*
* @var Link[]
*/
public $links = UNDEFINED;
public $links = Generator::UNDEFINED;
/**
* Reusable Callbacks.
*
* @var callable[]
*/
public $callbacks = UNDEFINED;
public $callbacks = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A "Contact Object": https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#contact-object
@ -19,21 +21,21 @@ class Contact extends AbstractAnnotation
*
* @var string
*/
public $name = UNDEFINED;
public $name = Generator::UNDEFINED;
/**
* The URL pointing to the contact information.
*
* @var string
*/
public $url = UNDEFINED;
public $url = Generator::UNDEFINED;
/**
* The email address of the contact person/organization.
*
* @var string
*/
public $email = UNDEFINED;
public $email = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* The discriminator is a specific object in a schema which is used to inform the consumer of
@ -23,14 +25,14 @@ class Discriminator extends AbstractAnnotation
*
* @var string
*/
public $propertyName = UNDEFINED;
public $propertyName = Generator::UNDEFINED;
/**
* An object to hold mappings between payload values and schema names or references.
*
* @var string[]
*/
public $mapping = UNDEFINED;
public $mapping = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
*/
@ -16,21 +18,21 @@ class Examples extends AbstractAnnotation
*
* @var string
*/
public $ref = UNDEFINED;
public $ref = Generator::UNDEFINED;
/**
* The key into Components->examples array.
*
* @var string
*/
public $example = UNDEFINED;
public $example = Generator::UNDEFINED;
/**
* Short description for the example.
*
* @var string
*/
public $summary = UNDEFINED;
public $summary = Generator::UNDEFINED;
/**
* Embedded literal example. The value field and externalValue field are
@ -40,7 +42,7 @@ class Examples extends AbstractAnnotation
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* Embedded literal example.
@ -51,7 +53,7 @@ class Examples extends AbstractAnnotation
*
* @var string
*/
public $value = UNDEFINED;
public $value = Generator::UNDEFINED;
/**
* A URL that points to the literal example. This provides the
@ -61,7 +63,7 @@ class Examples extends AbstractAnnotation
*
* @var string
*/
public $externalValue = UNDEFINED;
public $externalValue = Generator::UNDEFINED;
public static $_types = [
'summary' => 'string',

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* Allows referencing an external resource for extended documentation.
@ -19,14 +21,14 @@ class ExternalDocumentation extends AbstractAnnotation
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* The URL for the target documentation.
*
* @var string
*/
public $url = UNDEFINED;
public $url = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Configuration details for a supported OAuth Flow
* [OAuth Flow Object](https://swagger.io/specification/#oauthFlowObject).
@ -20,7 +22,7 @@ class Flow extends AbstractAnnotation
*
* @var string
*/
public $authorizationUrl = UNDEFINED;
public $authorizationUrl = Generator::UNDEFINED;
/**
* The token URL to be used for this flow.
@ -28,7 +30,7 @@ class Flow extends AbstractAnnotation
*
* @var string
*/
public $tokenUrl = UNDEFINED;
public $tokenUrl = Generator::UNDEFINED;
/**
* The URL to be used for obtaining refresh tokens.
@ -36,19 +38,19 @@ class Flow extends AbstractAnnotation
*
* @var string
*/
public $refreshUrl = UNDEFINED;
public $refreshUrl = Generator::UNDEFINED;
/**
* Flow name. One of ['implicit', 'password', 'authorizationCode', 'clientCredentials'].
*
* @var string
*/
public $flow = UNDEFINED;
public $flow = Generator::UNDEFINED;
/**
* The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it.
*/
public $scopes = UNDEFINED;
public $scopes = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -5,6 +5,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
*
@ -17,38 +19,38 @@ class Header extends AbstractAnnotation
*
* @var string
*/
public $ref = UNDEFINED;
public $ref = Generator::UNDEFINED;
/**
* @var string
*/
public $header = UNDEFINED;
public $header = Generator::UNDEFINED;
/**
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* A brief description of the parameter. This could contain examples of use. CommonMark syntax MAY be used for rich text representation.
*
* @var bool
*/
public $required = UNDEFINED;
public $required = Generator::UNDEFINED;
/**
* Schema object.
*
* @var \OpenApi\Annotations\Schema
*/
public $schema = UNDEFINED;
public $schema = Generator::UNDEFINED;
/**
* Specifies that a parameter is deprecated and SHOULD be transitioned out of usage.
*
* @var bool
*/
public $deprecated = UNDEFINED;
public $deprecated = Generator::UNDEFINED;
/**
* Sets the ability to pass empty-valued parameters.
@ -58,7 +60,7 @@ class Header extends AbstractAnnotation
*
* @var bool
*/
public $allowEmptyValue = UNDEFINED;
public $allowEmptyValue = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* An "Info Object": https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#info-object
@ -20,42 +22,42 @@ class Info extends AbstractAnnotation
*
* @var string
*/
public $title = UNDEFINED;
public $title = Generator::UNDEFINED;
/**
* A short description of the application. CommonMark syntax may be used for rich text representation.
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* A URL to the Terms of Service for the API. must be in the format of a url.
*
* @var string
*/
public $termsOfService = UNDEFINED;
public $termsOfService = Generator::UNDEFINED;
/**
* The contact information for the exposed API.
*
* @var Contact
*/
public $contact = UNDEFINED;
public $contact = Generator::UNDEFINED;
/**
* The license information for the exposed API.
*
* @var License
*/
public $license = UNDEFINED;
public $license = Generator::UNDEFINED;
/**
* The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API implementation version).
*
* @var string
*/
public $version = UNDEFINED;
public $version = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* Shorthand for a json response.
@ -18,12 +20,12 @@ class JsonContent extends Schema
/**
* @var object
*/
public $example = UNDEFINED;
public $example = Generator::UNDEFINED;
/**
* @var object
*/
public $examples = UNDEFINED;
public $examples = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* License information for the exposed API.
@ -19,14 +21,14 @@ class License extends AbstractAnnotation
*
* @var string
*/
public $name = UNDEFINED;
public $name = Generator::UNDEFINED;
/**
* A URL to the license used for the API.
*
* @var string
*/
public $url = UNDEFINED;
public $url = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A "Link Object" https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#link-object
@ -23,14 +25,14 @@ class Link extends AbstractAnnotation
*
* @var string
*/
public $ref = UNDEFINED;
public $ref = Generator::UNDEFINED;
/**
* The key into MediaType->links array.
*
* @var string
*/
public $link = UNDEFINED;
public $link = Generator::UNDEFINED;
/**
* A relative or absolute reference to an OA operation.
@ -39,7 +41,7 @@ class Link extends AbstractAnnotation
*
* @var string
*/
public $operationRef = UNDEFINED;
public $operationRef = Generator::UNDEFINED;
/**
* The name of an existing, resolvable OA operation, as defined with a unique operationId.
@ -47,19 +49,19 @@ class Link extends AbstractAnnotation
*
* @var string
*/
public $operationId = UNDEFINED;
public $operationId = Generator::UNDEFINED;
/**
* A map representing parameters to pass to an operation as specified with operationId or identified via operationRef.
* The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and passed to the linked operation.
* The parameter name can be qualified using the parameter location [{in}.]{name} for operations that use the same parameter name in different locations (e.g. path.id).
*/
public $parameters = UNDEFINED;
public $parameters = Generator::UNDEFINED;
/**
* A literal value or {expression} to use as a request body when calling the target operation.
*/
public $requestBody = UNDEFINED;
public $requestBody = Generator::UNDEFINED;
/**
* A description of the link.
@ -67,14 +69,14 @@ class Link extends AbstractAnnotation
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* A server object to be used by the target operation.
*
* @var Server
*/
public $server = UNDEFINED;
public $server = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A "Media Type Object" https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#media-type-object
@ -20,14 +22,14 @@ class MediaType extends AbstractAnnotation
*
* @var string
*/
public $mediaType = UNDEFINED;
public $mediaType = Generator::UNDEFINED;
/**
* The schema defining the type used for the request body.
*
* @var Schema
*/
public $schema = UNDEFINED;
public $schema = Generator::UNDEFINED;
/**
* Example of the media type.
@ -35,7 +37,7 @@ class MediaType extends AbstractAnnotation
* The example object is mutually exclusive of the examples object.
* Furthermore, if referencing a schema which contains an example, the example value shall override the example provided by the schema.
*/
public $example = UNDEFINED;
public $example = Generator::UNDEFINED;
/**
* Examples of the media type.
@ -45,14 +47,14 @@ class MediaType extends AbstractAnnotation
*
* @var array
*/
public $examples = UNDEFINED;
public $examples = Generator::UNDEFINED;
/**
* A map between a property name and its encoding information.
* The key, being the property name, must exist in the schema as a property.
* The encoding object shall only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
*/
public $encoding = UNDEFINED;
public $encoding = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -7,6 +7,7 @@
namespace OpenApi\Annotations;
use OpenApi\Analysis;
use OpenApi\Generator;
use OpenApi\Logger;
use OpenApi\Util;
@ -32,7 +33,7 @@ class OpenApi extends AbstractAnnotation
*
* @var Info
*/
public $info = UNDEFINED;
public $info = Generator::UNDEFINED;
/**
* An array of Server Objects, which provide connectivity information to a target server.
@ -40,21 +41,21 @@ class OpenApi extends AbstractAnnotation
*
* @var Server[]
*/
public $servers = UNDEFINED;
public $servers = Generator::UNDEFINED;
/**
* The available paths and operations for the API.
*
* @var PathItem[]
*/
public $paths = UNDEFINED;
public $paths = Generator::UNDEFINED;
/**
* An element to hold various components for the specification.
*
* @var Components
*/
public $components = UNDEFINED;
public $components = Generator::UNDEFINED;
/**
* Lists the required security schemes to execute this operation.
@ -70,7 +71,7 @@ class OpenApi extends AbstractAnnotation
*
* @var array
*/
public $security = UNDEFINED;
public $security = Generator::UNDEFINED;
/**
* A list of tags used by the specification with additional metadata.
@ -81,19 +82,19 @@ class OpenApi extends AbstractAnnotation
*
* @var Tag[]
*/
public $tags = UNDEFINED;
public $tags = Generator::UNDEFINED;
/**
* Additional external documentation.
*
* @var ExternalDocumentation
*/
public $externalDocs = UNDEFINED;
public $externalDocs = Generator::UNDEFINED;
/**
* @var Analysis
*/
public $_analysis = UNDEFINED;
public $_analysis = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,7 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
use OpenApi\Logger;
/**
@ -22,7 +23,7 @@ abstract class Operation extends AbstractAnnotation
*
* @var string
*/
public $path = UNDEFINED;
public $path = Generator::UNDEFINED;
/**
* A list of tags for API documentation control.
@ -30,7 +31,7 @@ abstract class Operation extends AbstractAnnotation
*
* @var string[]
*/
public $tags = UNDEFINED;
public $tags = Generator::UNDEFINED;
/**
* Key in the OpenApi "Path Item Object" for this operation.
@ -38,14 +39,14 @@ abstract class Operation extends AbstractAnnotation
*
* @var string
*/
public $method = UNDEFINED;
public $method = Generator::UNDEFINED;
/**
* A short summary of what the operation does.
*
* @var string
*/
public $summary = UNDEFINED;
public $summary = Generator::UNDEFINED;
/**
* A verbose explanation of the operation behavior.
@ -53,14 +54,14 @@ abstract class Operation extends AbstractAnnotation
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* Additional external documentation for this operation.
*
* @var ExternalDocumentation
*/
public $externalDocs = UNDEFINED;
public $externalDocs = Generator::UNDEFINED;
/**
* Unique string used to identify the operation.
@ -69,7 +70,7 @@ abstract class Operation extends AbstractAnnotation
*
* @var string
*/
public $operationId = UNDEFINED;
public $operationId = Generator::UNDEFINED;
/**
* A list of parameters that are applicable for this operation.
@ -80,7 +81,7 @@ abstract class Operation extends AbstractAnnotation
*
* @var Parameter[]
*/
public $parameters = UNDEFINED;
public $parameters = Generator::UNDEFINED;
/**
* The request body applicable for this operation.
@ -89,14 +90,14 @@ abstract class Operation extends AbstractAnnotation
*
* @var RequestBody
*/
public $requestBody = UNDEFINED;
public $requestBody = Generator::UNDEFINED;
/**
* The list of possible responses as they are returned from executing this operation.
*
* @var \OpenApi\Annotations\Response[]
*/
public $responses = UNDEFINED;
public $responses = Generator::UNDEFINED;
/**
* A map of possible out-of band callbacks related to the parent operation.
@ -106,7 +107,7 @@ abstract class Operation extends AbstractAnnotation
*
* @var callable[]
*/
public $callbacks = UNDEFINED;
public $callbacks = Generator::UNDEFINED;
/**
* Declares this operation to be deprecated.
@ -115,7 +116,7 @@ abstract class Operation extends AbstractAnnotation
*
* @var bool
*/
public $deprecated = UNDEFINED;
public $deprecated = Generator::UNDEFINED;
/**
* A declaration of which security mechanisms can be used for this operation.
@ -126,7 +127,7 @@ abstract class Operation extends AbstractAnnotation
*
* @var array
*/
public $security = UNDEFINED;
public $security = Generator::UNDEFINED;
/**
* An alternative server array to service this operation.
@ -134,7 +135,7 @@ abstract class Operation extends AbstractAnnotation
*
* @var Server[]
*/
public $servers = UNDEFINED;
public $servers = Generator::UNDEFINED;
/**
* {@inheritdoc}
@ -190,9 +191,9 @@ abstract class Operation extends AbstractAnnotation
return true;
}
$valid = parent::validate($parents, $skip);
if ($this->responses !== UNDEFINED) {
if ($this->responses !== Generator::UNDEFINED) {
foreach ($this->responses as $response) {
if ($response->response !== UNDEFINED && $response->response !== 'default' && preg_match('/^([12345]{1}[0-9]{2})|([12345]{1}XX)$/', (string) $response->response) === 0) {
if ($response->response !== Generator::UNDEFINED && $response->response !== 'default' && preg_match('/^([12345]{1}[0-9]{2})|([12345]{1}XX)$/', (string) $response->response) === 0) {
Logger::notice('Invalid value "'.$response->response.'" for '.$response->_identity([]).'->response, expecting "default", a HTTP Status Code or HTTP Status Code range definition in '.$response->_context);
}
}

View File

@ -6,6 +6,7 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
use OpenApi\Logger;
/**
@ -21,14 +22,14 @@ class Parameter extends AbstractAnnotation
*
* @var string
*/
public $ref = UNDEFINED;
public $ref = Generator::UNDEFINED;
/**
* The key into Components->parameters or PathItem->parameters array.
*
* @var string
*/
public $parameter = UNDEFINED;
public $parameter = Generator::UNDEFINED;
/**
* The name of the parameter.
@ -39,7 +40,7 @@ class Parameter extends AbstractAnnotation
*
* @var string
*/
public $name = UNDEFINED;
public $name = Generator::UNDEFINED;
/**
* The location of the parameter.
@ -47,7 +48,7 @@ class Parameter extends AbstractAnnotation
*
* @var string
*/
public $in = UNDEFINED;
public $in = Generator::UNDEFINED;
/**
* A brief description of the parameter.
@ -56,7 +57,7 @@ class Parameter extends AbstractAnnotation
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* Determines whether this parameter is mandatory.
@ -65,14 +66,14 @@ class Parameter extends AbstractAnnotation
*
* @var bool
*/
public $required = UNDEFINED;
public $required = Generator::UNDEFINED;
/**
* Specifies that a parameter is deprecated and should be transitioned out of usage.
*
* @var bool
*/
public $deprecated = UNDEFINED;
public $deprecated = Generator::UNDEFINED;
/**
* Sets the ability to pass empty-valued parameters.
@ -81,7 +82,7 @@ class Parameter extends AbstractAnnotation
*
* @var bool
*/
public $allowEmptyValue = UNDEFINED;
public $allowEmptyValue = Generator::UNDEFINED;
/**
* Describes how the parameter value will be serialized depending on the type of the parameter value.
@ -89,7 +90,7 @@ class Parameter extends AbstractAnnotation
*
* @var string
*/
public $style = UNDEFINED;
public $style = Generator::UNDEFINED;
/**
* When this is true, parameter values of type array or object generate separate parameters for each value of the array or key-value pair of the map.
@ -99,7 +100,7 @@ class Parameter extends AbstractAnnotation
*
* @var bool
*/
public $explode = UNDEFINED;
public $explode = Generator::UNDEFINED;
/**
* Determines whether the parameter value should allow reserved characters, as defined by RFC3986 :/?#[]@!$&'()*+,;= to be included without percent-encoding.
@ -108,14 +109,14 @@ class Parameter extends AbstractAnnotation
*
* @var bool
*/
public $allowReserved = UNDEFINED;
public $allowReserved = Generator::UNDEFINED;
/**
* The schema defining the type used for the parameter.
*
* @var Schema
*/
public $schema = UNDEFINED;
public $schema = Generator::UNDEFINED;
/**
* Example of the media type.
@ -124,7 +125,7 @@ class Parameter extends AbstractAnnotation
* Furthermore, if referencing a schema which contains an example, the example value shall override the example provided by the schema.
* To represent examples of media types that cannot naturally be represented in JSON or YAML, a string value can contain the example with escaping where necessary.
*/
public $example = UNDEFINED;
public $example = Generator::UNDEFINED;
/**
* Examples of the media type.
@ -134,7 +135,7 @@ class Parameter extends AbstractAnnotation
*
* @var array
*/
public $examples = UNDEFINED;
public $examples = Generator::UNDEFINED;
/**
* A map containing the representations for the parameter.
@ -143,23 +144,23 @@ class Parameter extends AbstractAnnotation
*
* @var MediaType[]
*/
public $content = UNDEFINED;
public $content = Generator::UNDEFINED;
/**
* Path-style parameters defined by https://tools.ietf.org/html/rfc6570#section-3.2.7.
*/
public $matrix = UNDEFINED;
public $matrix = Generator::UNDEFINED;
/**
* Label style parameters defined by https://tools.ietf.org/html/rfc6570#section-3.2.5.
*/
public $label = UNDEFINED;
public $label = Generator::UNDEFINED;
/**
* Form style parameters defined by https://tools.ietf.org/html/rfc6570#section-3.2.8
* This option replaces collectionFormat with a csv (when explode is false) or multi (when explode is true) value from OpenAPI 2.0.
*/
public $form = UNDEFINED;
public $form = Generator::UNDEFINED;
/**
* Simple style parameters defined by https://tools.ietf.org/html/rfc6570#section-3.2.2
@ -167,7 +168,7 @@ class Parameter extends AbstractAnnotation
*
* @var array
*/
public $simple = UNDEFINED;
public $simple = Generator::UNDEFINED;
/**
* Space separated array values.
@ -175,7 +176,7 @@ class Parameter extends AbstractAnnotation
*
* @var array
*/
public $spaceDelimited = UNDEFINED;
public $spaceDelimited = Generator::UNDEFINED;
/**
* Pipe separated array values.
@ -183,12 +184,12 @@ class Parameter extends AbstractAnnotation
*
* @var array
*/
public $pipeDelimited = UNDEFINED;
public $pipeDelimited = Generator::UNDEFINED;
/**
* Provides a simple way of rendering nested objects using form parameters.
*/
public $deepObject = UNDEFINED;
public $deepObject = Generator::UNDEFINED;
/**
* {@inheritdoc}
@ -240,9 +241,9 @@ class Parameter extends AbstractAnnotation
return true;
}
$valid = parent::validate($parents, $skip, $ref);
if ($this->ref === UNDEFINED) {
if ($this->ref === Generator::UNDEFINED) {
if ($this->in === 'body') {
if ($this->schema === UNDEFINED) {
if ($this->schema === Generator::UNDEFINED) {
Logger::notice('Field "schema" is required when '.$this->identity().' is in "'.$this->in.'" in '.$this->_context);
$valid = false;
}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A "Path Item Object": https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#path-item-object
@ -20,84 +22,84 @@ class PathItem extends AbstractAnnotation
*
* @var string
*/
public $ref = UNDEFINED;
public $ref = Generator::UNDEFINED;
/**
* key for the Path Object (OpenApi->paths array).
*
* @var string
*/
public $path = UNDEFINED;
public $path = Generator::UNDEFINED;
/**
* An optional, string summary, intended to apply to all operations in this path.
*
* @var string
*/
public $summary = UNDEFINED;
public $summary = Generator::UNDEFINED;
/**
* A definition of a GET operation on this path.
*
* @var Get
*/
public $get = UNDEFINED;
public $get = Generator::UNDEFINED;
/**
* A definition of a PUT operation on this path.
*
* @var Put
*/
public $put = UNDEFINED;
public $put = Generator::UNDEFINED;
/**
* A definition of a POST operation on this path.
*
* @var Post
*/
public $post = UNDEFINED;
public $post = Generator::UNDEFINED;
/**
* A definition of a DELETE operation on this path.
*
* @var Delete
*/
public $delete = UNDEFINED;
public $delete = Generator::UNDEFINED;
/**
* A definition of a OPTIONS operation on this path.
*
* @var Options
*/
public $options = UNDEFINED;
public $options = Generator::UNDEFINED;
/**
* A definition of a HEAD operation on this path.
*
* @var Head
*/
public $head = UNDEFINED;
public $head = Generator::UNDEFINED;
/**
* A definition of a PATCH operation on this path.
*
* @var Patch
*/
public $patch = UNDEFINED;
public $patch = Generator::UNDEFINED;
/**
* A definition of a TRACE operation on this path.
*
* @var Trace
*/
public $trace = UNDEFINED;
public $trace = Generator::UNDEFINED;
/**
* An alternative server array to service all operations in this path.
*
* @var Server[]
*/
public $servers = UNDEFINED;
public $servers = Generator::UNDEFINED;
/**
* A list of parameters that are applicable for all the operations described under this path.
@ -108,7 +110,7 @@ class PathItem extends AbstractAnnotation
*
* @var Parameter[]
*/
public $parameters = UNDEFINED;
public $parameters = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
*/
@ -16,7 +18,7 @@ class Property extends Schema
*
* @var string
*/
public $property = UNDEFINED;
public $property = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A "Response Object": https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#requestBodyObject
@ -15,14 +17,14 @@ namespace OpenApi\Annotations;
*/
class RequestBody extends AbstractAnnotation
{
public $ref = UNDEFINED;
public $ref = Generator::UNDEFINED;
/**
* Request body model name.
*
* @var string
*/
public $request = UNDEFINED;
public $request = Generator::UNDEFINED;
/**
* A brief description of the parameter.
@ -31,7 +33,7 @@ class RequestBody extends AbstractAnnotation
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* Determines whether this parameter is mandatory.
@ -40,7 +42,7 @@ class RequestBody extends AbstractAnnotation
*
* @var bool
*/
public $required = UNDEFINED;
public $required = Generator::UNDEFINED;
/**
* The content of the request body.
@ -49,7 +51,7 @@ class RequestBody extends AbstractAnnotation
*
* @var MediaType[]
*/
public $content = UNDEFINED;
public $content = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A "Response Object": https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#response-object
@ -19,14 +21,14 @@ class Response extends AbstractAnnotation
*
* @var string
*/
public $ref = UNDEFINED;
public $ref = Generator::UNDEFINED;
/**
* The key into Operations->responses array.
*
* @var string a HTTP Status Code or "default"
*/
public $response = UNDEFINED;
public $response = Generator::UNDEFINED;
/**
* A short description of the response.
@ -34,7 +36,7 @@ class Response extends AbstractAnnotation
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* Maps a header name to its definition.
@ -43,7 +45,7 @@ class Response extends AbstractAnnotation
*
* @var Header[]
*/
public $headers = UNDEFINED;
public $headers = Generator::UNDEFINED;
/**
* A map containing descriptions of potential response payloads.
@ -52,7 +54,7 @@ class Response extends AbstractAnnotation
*
* @var MediaType[]
*/
public $content = UNDEFINED;
public $content = Generator::UNDEFINED;
/**
* A map of operations links that can be followed from the response.
@ -60,7 +62,7 @@ class Response extends AbstractAnnotation
*
* @var array
*/
public $links = UNDEFINED;
public $links = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,7 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
use OpenApi\Logger;
/**
@ -25,169 +26,169 @@ class Schema extends AbstractAnnotation
*
* @var string
*/
public $ref = UNDEFINED;
public $ref = Generator::UNDEFINED;
/**
* The key into Components->schemas array.
*
* @var string
*/
public $schema = UNDEFINED;
public $schema = Generator::UNDEFINED;
/**
* Can be used to decorate a user interface with information about the data produced by this user interface. preferrably be short.
*
* @var string
*/
public $title = UNDEFINED;
public $title = Generator::UNDEFINED;
/**
* A description will provide explanation about the purpose of the instance described by this schema.
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* An object instance is valid against "maxProperties" if its number of properties is less than, or equal to, the value of this property.
*
* @var int
*/
public $maxProperties = UNDEFINED;
public $maxProperties = Generator::UNDEFINED;
/**
* An object instance is valid against "minProperties" if its number of properties is greater than, or equal to, the value of this property.
*
* @var int
*/
public $minProperties = UNDEFINED;
public $minProperties = Generator::UNDEFINED;
/**
* An object instance is valid against this property if its property set contains all elements in this property's array value.
*
* @var string[]
*/
public $required = UNDEFINED;
public $required = Generator::UNDEFINED;
/**
* @var Property[]
*/
public $properties = UNDEFINED;
public $properties = Generator::UNDEFINED;
/**
* The type of the schema/property. The value MUST be one of "string", "number", "integer", "boolean", "array" or "object".
*
* @var string
*/
public $type = UNDEFINED;
public $type = Generator::UNDEFINED;
/**
* The extending format for the previously mentioned type. See Data Type Formats for further details.
*
* @var string
*/
public $format = UNDEFINED;
public $format = Generator::UNDEFINED;
/**
* Required if type is "array". Describes the type of items in the array.
*
* @var Items
*/
public $items = UNDEFINED;
public $items = Generator::UNDEFINED;
/**
* @var string Determines the format of the array if type array is used. Possible values are: csv - comma separated values foo,bar. ssv - space separated values foo bar. tsv - tab separated values foo\tbar. pipes - pipe separated values foo|bar. multi - corresponds to multiple parameter instances instead of multiple values for a single instance foo=bar&foo=baz. This is valid only for parameters in "query" or "formData". Default value is csv.
*/
public $collectionFormat = UNDEFINED;
public $collectionFormat = Generator::UNDEFINED;
/**
* Sets a default value to the parameter. The type of the value depends on the defined type. See http://json-schema.org/latest/json-schema-validation.html#anchor101.
*/
public $default = UNDEFINED;
public $default = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor17.
*
* @var number
*/
public $maximum = UNDEFINED;
public $maximum = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor17.
*
* @var bool
*/
public $exclusiveMaximum = UNDEFINED;
public $exclusiveMaximum = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor21.
*
* @var number
*/
public $minimum = UNDEFINED;
public $minimum = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor21.
*
* @var bool
*/
public $exclusiveMinimum = UNDEFINED;
public $exclusiveMinimum = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor26.
*
* @var int
*/
public $maxLength = UNDEFINED;
public $maxLength = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor29.
*
* @var int
*/
public $minLength = UNDEFINED;
public $minLength = Generator::UNDEFINED;
/**
* A string instance is considered valid if the regular expression matches the instance successfully.
*
* @var string
*/
public $pattern = UNDEFINED;
public $pattern = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor42.
*
* @var int
*/
public $maxItems = UNDEFINED;
public $maxItems = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor45.
*
* @var int
*/
public $minItems = UNDEFINED;
public $minItems = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor49.
*
* @var bool
*/
public $uniqueItems = UNDEFINED;
public $uniqueItems = Generator::UNDEFINED;
/**
* See http://json-schema.org/latest/json-schema-validation.html#anchor76.
*
* @var array
*/
public $enum = UNDEFINED;
public $enum = Generator::UNDEFINED;
/**
* A numeric instance is valid against "multipleOf" if the result of the division of the instance by this property's value is an integer.
*
* @var number
*/
public $multipleOf = UNDEFINED;
public $multipleOf = Generator::UNDEFINED;
/**
* Adds support for polymorphism.
@ -196,7 +197,7 @@ class Schema extends AbstractAnnotation
*
* @var Discriminator
*/
public $discriminator = UNDEFINED;
public $discriminator = Generator::UNDEFINED;
/**
* Relevant only for Schema "properties" definitions.
@ -208,7 +209,7 @@ class Schema extends AbstractAnnotation
*
* @var bool
*/
public $readOnly = UNDEFINED;
public $readOnly = Generator::UNDEFINED;
/**
* Relevant only for Schema "properties" definitions.
@ -220,7 +221,7 @@ class Schema extends AbstractAnnotation
*
* @var bool
*/
public $writeOnly = UNDEFINED;
public $writeOnly = Generator::UNDEFINED;
/**
* This may be used only on properties schemas.
@ -229,20 +230,20 @@ class Schema extends AbstractAnnotation
*
* @var Xml
*/
public $xml = UNDEFINED;
public $xml = Generator::UNDEFINED;
/**
* Additional external documentation for this schema.
*
* @var ExternalDocumentation
*/
public $externalDocs = UNDEFINED;
public $externalDocs = Generator::UNDEFINED;
/**
* A free-form property to include an example of an instance for this schema.
* To represent examples that cannot be naturally represented in JSON or YAML, a string value can be used to contain the example with escaping where necessary.
*/
public $example = UNDEFINED;
public $example = Generator::UNDEFINED;
/**
* Allows sending a null value for the defined schema.
@ -250,7 +251,7 @@ class Schema extends AbstractAnnotation
*
* @var bool
*/
public $nullable = UNDEFINED;
public $nullable = Generator::UNDEFINED;
/**
* Specifies that a schema is deprecated and should be transitioned out of usage.
@ -258,70 +259,70 @@ class Schema extends AbstractAnnotation
*
* @var bool
*/
public $deprecated = UNDEFINED;
public $deprecated = Generator::UNDEFINED;
/**
* An instance validates successfully against this property if it validates successfully against all schemas defined by this property's value.
*
* @var Schema[]
*/
public $allOf = UNDEFINED;
public $allOf = Generator::UNDEFINED;
/**
* An instance validates successfully against this property if it validates successfully against at least one schema defined by this property's value.
*
* @var Schema[]
*/
public $anyOf = UNDEFINED;
public $anyOf = Generator::UNDEFINED;
/**
* An instance validates successfully against this property if it validates successfully against exactly one schema defined by this property's value.
*
* @var Schema[]
*/
public $oneOf = UNDEFINED;
public $oneOf = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.29.
*/
public $not = UNDEFINED;
public $not = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#anchor64.
*
* @var bool|object
*/
public $additionalProperties = UNDEFINED;
public $additionalProperties = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.10.
*/
public $additionalItems = UNDEFINED;
public $additionalItems = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.14.
*/
public $contains = UNDEFINED;
public $contains = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.19.
*/
public $patternProperties = UNDEFINED;
public $patternProperties = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.21.
*/
public $dependencies = UNDEFINED;
public $dependencies = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.22.
*/
public $propertyNames = UNDEFINED;
public $propertyNames = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.24.
*/
public $const = UNDEFINED;
public $const = Generator::UNDEFINED;
/**
* {@inheritdoc}
@ -371,7 +372,7 @@ class Schema extends AbstractAnnotation
public function validate(array $parents = [], array $skip = [], string $ref = ''): bool
{
if ($this->type === 'array' && $this->items === UNDEFINED) {
if ($this->type === 'array' && $this->items === Generator::UNDEFINED) {
Logger::notice('@OA\\Items() is required when '.$this->identity().' has type "array" in '.$this->_context);
return false;

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A "Security Scheme Object":
@ -18,49 +20,49 @@ class SecurityScheme extends AbstractAnnotation
*
* @var string
*/
public $ref = UNDEFINED;
public $ref = Generator::UNDEFINED;
/**
* The key into OpenApi->security array.
*
* @var string
*/
public $securityScheme = UNDEFINED;
public $securityScheme = Generator::UNDEFINED;
/**
* The type of the security scheme.
*
* @var string
*/
public $type = UNDEFINED;
public $type = Generator::UNDEFINED;
/**
* A short description for security scheme.
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* The name of the header or query parameter to be used.
*
* @var string
*/
public $name = UNDEFINED;
public $name = Generator::UNDEFINED;
/**
* Required The location of the API key.
*
* @var string
*/
public $in = UNDEFINED;
public $in = Generator::UNDEFINED;
/**
* The flow used by the OAuth2 security scheme.
*
* @var Flow[]
*/
public $flows = UNDEFINED;
public $flows = Generator::UNDEFINED;
/**
* A hint to the client to identify how the bearer token is formatted. Bearer tokens are usually generated by an
@ -68,7 +70,7 @@ class SecurityScheme extends AbstractAnnotation
*
* @var string
*/
public $bearerFormat = UNDEFINED;
public $bearerFormat = Generator::UNDEFINED;
/**
* The name of the HTTP Authorization scheme.
@ -77,14 +79,14 @@ class SecurityScheme extends AbstractAnnotation
*
* @var string
*/
public $scheme = UNDEFINED;
public $scheme = Generator::UNDEFINED;
/**
* OpenId Connect URL to discover OAuth2 configuration values. This MUST be in the form of a URL.
*
* @var string
*/
public $openIdConnectUrl = UNDEFINED;
public $openIdConnectUrl = Generator::UNDEFINED;
/**
* {@inheritdoc}
@ -124,7 +126,7 @@ class SecurityScheme extends AbstractAnnotation
$unmerged = parent::merge($annotations, $ignore);
if ($this->type === 'oauth2') {
$this->name = UNDEFINED;
$this->name = Generator::UNDEFINED;
}
return $unmerged;

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A Server Object https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#server-object
@ -20,7 +22,7 @@ class Server extends AbstractAnnotation
*
* @var string
*/
public $url = UNDEFINED;
public $url = Generator::UNDEFINED;
/**
* An optional string describing the host designated by the URL.
@ -28,7 +30,7 @@ class Server extends AbstractAnnotation
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* A map between a variable name and its value.
@ -36,7 +38,7 @@ class Server extends AbstractAnnotation
*
* @var array
*/
public $variables = UNDEFINED;
public $variables = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* A Server Variable Object https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#server-variable-object
@ -18,14 +20,14 @@ class ServerVariable extends AbstractAnnotation
*
* @var string
*/
public $serverVariable = UNDEFINED;
public $serverVariable = Generator::UNDEFINED;
/**
* An enumeration of string values to be used if the substitution options are from a limited set.
*
* @var string[]
*/
public $enum = UNDEFINED;
public $enum = Generator::UNDEFINED;
/**
* The default value to use for substitution, and to send, if an alternate value is not supplied.
@ -33,7 +35,7 @@ class ServerVariable extends AbstractAnnotation
*
* @var string
*/
public $default = UNDEFINED;
public $default = Generator::UNDEFINED;
/**
* A map between a variable name and its value.
@ -41,7 +43,7 @@ class ServerVariable extends AbstractAnnotation
*
* @var array
*/
public $variables = UNDEFINED;
public $variables = Generator::UNDEFINED;
/**
* An optional description for the server variable.
@ -49,7 +51,7 @@ class ServerVariable extends AbstractAnnotation
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
*
@ -18,21 +20,21 @@ class Tag extends AbstractAnnotation
*
* @var string
*/
public $name = UNDEFINED;
public $name = Generator::UNDEFINED;
/**
* A short description for the tag. GFM syntax can be used for rich text representation.
*
* @var string
*/
public $description = UNDEFINED;
public $description = Generator::UNDEFINED;
/**
* Additional external documentation for this tag.
*
* @var ExternalDocumentation
*/
public $externalDocs = UNDEFINED;
public $externalDocs = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
*
@ -18,35 +20,35 @@ class Xml extends AbstractAnnotation
*
* @var string
*/
public $name = UNDEFINED;
public $name = Generator::UNDEFINED;
/**
* The URL of the namespace definition. Value SHOULD be in the form of a URL.
*
* @var string
*/
public $namespace = UNDEFINED;
public $namespace = Generator::UNDEFINED;
/**
* The prefix to be used for the name.
*
* @var string
*/
public $prefix = UNDEFINED;
public $prefix = Generator::UNDEFINED;
/**
* Declares whether the property definition translates to an attribute instead of an element. Default value is false.
*
* @var bool
*/
public $attribute = UNDEFINED;
public $attribute = Generator::UNDEFINED;
/**
* MAY be used only for an array definition. Signifies whether the array is wrapped (for example, <books><book/><book/></books>) or unwrapped (<book/><book/>). Default value is false. The definition takes effect only when defined alongside type being array (outside the items).
*
* @var bool
*/
public $wrapped = UNDEFINED;
public $wrapped = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -6,6 +6,8 @@
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
* Shorthand for a xml response.
@ -17,7 +19,7 @@ class XmlContent extends Schema
/**
* @var object
*/
public $examples = UNDEFINED;
public $examples = Generator::UNDEFINED;
/**
* {@inheritdoc}

View File

@ -172,7 +172,7 @@ class Context
{
$content = $this->phpdocContent();
if (!$content) {
return UNDEFINED;
return Generator::UNDEFINED;
}
$lines = preg_split('/(\n|\r\n)/', $content);
$summary = '';
@ -184,7 +184,7 @@ class Context
}
$summary = trim($summary);
if ($summary === '') {
return UNDEFINED;
return Generator::UNDEFINED;
}
return $summary;
@ -199,7 +199,7 @@ class Context
{
$summary = $this->phpdocSummary();
if (!$summary) {
return UNDEFINED;
return Generator::UNDEFINED;
}
if (false !== ($substr = substr($this->phpdocContent(), strlen($summary)))) {
$description = trim($substr);
@ -207,7 +207,7 @@ class Context
$description = '';
}
if ($description === '') {
return UNDEFINED;
return Generator::UNDEFINED;
}
return $description;
@ -241,7 +241,7 @@ class Context
}
$description = trim(implode("\n", $lines));
if ($description === '') {
return UNDEFINED;
return Generator::UNDEFINED;
}
return $description;

238
src/Generator.php Normal file
View File

@ -0,0 +1,238 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi;
use OpenApi\Annotations\OpenApi;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* OpenApi spec generator.
*
* Scans PHP source code and generates OpenApi specifications from the found OpenApi annotations.
*
* This is an object oriented alternative to using the now deprecated `\OpenApi\scan()` function and
* static class properties of the `Analyzer` and `Analysis` classes.
*
* The `aliases` property supersedes the `Analyser::$defaultImports`; `namespaces` maps to `Analysis::$whitelist`.
*/
class Generator
{
/** @var string Magic value to differentiate between null and undefined. */
public const UNDEFINED = '@OA\Generator::UNDEFINED🙈';
/** @var array Map of namespace aliases to be supported by doctrine. */
protected $aliases = null;
/** @var array List of annotation namespaces to be autoloaded by doctrine. */
protected $namespaces = null;
/** @var StaticAnalyser The configured analyzer. */
protected $analyser;
/** @var null|callable[] List of configured processors. */
protected $processors = null;
/** @var null|LoggerInterface PSR logger. */
protected $logger = null;
private $configStack;
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger;
// kinda config stack to stay BC...
$this->configStack = new class() {
private $defaultImports;
private $whitelist;
private $log;
public function push(Generator $generator): void
{
$this->defaultImports = Analyser::$defaultImports;
$this->whitelist = Analyser::$whitelist;
$this->log = Logger::getInstance()->log;
Analyser::$defaultImports = $generator->getAliases();
Analyser::$whitelist = $generator->getNamespaces();
if ($logger = $generator->getLogger()) {
Logger::getInstance()->log = function ($msg, $type) use ($logger) {
$context = [];
if ($msg instanceof \Exception) {
$context['exception'] = $exception = $msg;
$msg = $exception->getMessage();
}
$level = E_USER_ERROR == $type ? LogLevel::ERROR : LogLevel::INFO;
$logger->log($level, $msg, $context);
};
}
}
public function pop(): void
{
Analyser::$defaultImports = $this->defaultImports;
Analyser::$whitelist = $this->whitelist;
Logger::getInstance()->log = $this->log;
}
};
}
public function getAliases(): array
{
$aliases = null !== $this->aliases ? $this->aliases : Analyser::$defaultImports;
$aliases['oa'] = 'OpenApi\\Annotations';
return $aliases;
}
public function setAliases(?array $aliases): Generator
{
$this->aliases = $aliases;
return $this;
}
public function getNamespaces(): array
{
$namespaces = null !== $this->namespaces ? $this->namespaces : Analyser::$whitelist;
$namespaces = false !== $namespaces ? $namespaces : [];
$namespaces[] = 'OpenApi\\Annotations\\';
return $namespaces;
}
public function setNamespaces(?array $namespaces): Generator
{
$this->namespaces = $namespaces;
return $this;
}
public function getAnalyser(): StaticAnalyser
{
return $this->analyser ?: new StaticAnalyser();
}
public function setAnalyser(?StaticAnalyser $analyser): Generator
{
$this->analyser = $analyser;
return $this;
}
/**
* @return callable[]
*/
public function getProcessors(): array
{
return null !== $this->processors ? $this->processors : Analysis::processors();
}
/**
* @param null|callable[] $processors
*/
public function setProcessors(?array $processors): Generator
{
$this->processors = $processors;
return $this;
}
public function getLogger(): ?LoggerInterface
{
return $this->logger;
}
/**
* Static wrapper around `Generator::generate()`.
*
* @param iterable $sources PHP source files to scan.
* Supported sources:
* * string
* * \SplFileInfo
* * \Symfony\Component\Finder\Finder
* @param array $options
* aliases: null|array Defaults to `Analyser::$defaultImports`.
* namespaces: null|array Defaults to `Analyser::$whitelist`.
* analyser: null|StaticAnalyser Defaults to a new `StaticAnalyser`.
* analysis: null|Analysis Defaults to a new `Analysis`.
* processors: null|array Defaults to `Analysis::processors()`.
* validate: bool Defaults to `true`.
* logger: null|\Psr\Log\LoggerInterface If not set logging will use \OpenApi\Logger as before.
*/
public static function scan(iterable $sources, array $options = []): OpenApi
{
// merge with defaults
$config = $options + [
'aliases' => null,
'namespaces' => null,
'analyser' => null,
'analysis' => null,
'processors' => null,
'validate' => true,
];
return (new Generator())
->setAliases($config['aliases'])
->setNamespaces($config['namespaces'])
->setAnalyser($config['analyser'])
->setProcessors($config['processors'])
->generate($sources, $config['analysis'], $config['validate']);
}
/**
* Generate OpenAPI spec by scanning the given source files.
*
* @param iterable $sources PHP source files to scan.
* Supported sources:
* * string - file / directory name
* * \SplFileInfo
* * \Symfony\Component\Finder\Finder
* @param null|Analysis $analysis custom analysis instance
* @param bool $validate flag to enable/disable validation of the returned spec
*/
public function generate(iterable $sources, ?Analysis $analysis = null, bool $validate = true): OpenApi
{
$analysis = $analysis ?: new Analysis();
$this->configStack->push($this);
try {
$this->scanSources($sources, $analysis);
// post processing
$analysis->process($this->getProcessors());
// validation
if ($validate) {
$analysis->validate();
}
} finally {
$this->configStack->pop();
}
return $analysis->openapi;
}
protected function scanSources(iterable $sources, Analysis $analysis): void
{
$analyser = $this->getAnalyser();
foreach ($sources as $source) {
if (is_iterable($source)) {
$this->scanSources($source, $analysis);
} else {
$source = $source instanceof \SplFileInfo ? $source->getPathname() : realpath($source);
if (is_dir($source)) {
$this->scanSources(Util::finder($source), $analysis);
} else {
$analysis->addAnalysis($analyser->fromFile($source));
}
}
}
}
}

View File

@ -10,6 +10,8 @@ use Exception;
/**
* Logger reports the parser and validation messages.
*
* @deprecated use \OpenApi\Generator and PSR logger instead
*/
class Logger
{

View File

@ -7,6 +7,7 @@
namespace OpenApi\Processors;
use OpenApi\Analysis;
use OpenApi\Generator;
/**
* Use the parameter->name as keyfield (parameter->parameter) when used as reusable component (openapi->components->parameters).
@ -15,18 +16,18 @@ class AugmentParameters
{
public function __invoke(Analysis $analysis)
{
if ($analysis->openapi->components !== UNDEFINED && $analysis->openapi->components->parameters !== UNDEFINED) {
if ($analysis->openapi->components !== Generator::UNDEFINED && $analysis->openapi->components->parameters !== Generator::UNDEFINED) {
$keys = [];
$parametersWithoutKey = [];
foreach ($analysis->openapi->components->parameters as $parameter) {
if ($parameter->parameter !== UNDEFINED) {
if ($parameter->parameter !== Generator::UNDEFINED) {
$keys[$parameter->parameter] = $parameter;
} else {
$parametersWithoutKey[] = $parameter;
}
}
foreach ($parametersWithoutKey as $parameter) {
if ($parameter->name !== UNDEFINED && empty($keys[$parameter->name])) {
if ($parameter->name !== Generator::UNDEFINED && empty($keys[$parameter->name])) {
$parameter->parameter = $parameter->name;
$keys[$parameter->parameter] = $parameter;
}

View File

@ -12,6 +12,7 @@ use OpenApi\Annotations\Items;
use OpenApi\Annotations\Property;
use OpenApi\Annotations\Schema;
use OpenApi\Context;
use OpenApi\Generator;
use OpenApi\Util;
/**
@ -44,9 +45,9 @@ class AugmentProperties
public function __invoke(Analysis $analysis)
{
$refs = [];
if ($analysis->openapi->components !== UNDEFINED && $analysis->openapi->components->schemas !== UNDEFINED) {
if ($analysis->openapi->components !== Generator::UNDEFINED && $analysis->openapi->components->schemas !== Generator::UNDEFINED) {
foreach ($analysis->openapi->components->schemas as $schema) {
if ($schema->schema !== UNDEFINED) {
if ($schema->schema !== Generator::UNDEFINED) {
$refs[strtolower($schema->_context->fullyQualifiedName($schema->_context->class))]
= Components::SCHEMA_REF.Util::refEncode($schema->schema);
}
@ -57,14 +58,14 @@ class AugmentProperties
foreach ($allProperties as $property) {
$context = $property->_context;
// Use the property names for @OA\Property()
if ($property->property === UNDEFINED) {
if ($property->property === Generator::UNDEFINED) {
$property->property = $context->property;
}
if ($property->ref !== UNDEFINED) {
if ($property->ref !== Generator::UNDEFINED) {
continue;
}
$comment = str_replace("\r\n", "\n", (string) $context->comment);
if ($property->type === UNDEFINED && $context->type && $context->type !== UNDEFINED) {
if ($property->type === Generator::UNDEFINED && $context->type && $context->type !== Generator::UNDEFINED) {
if ($context->nullable === true) {
$property->nullable = true;
}
@ -73,19 +74,19 @@ class AugmentProperties
$this->applyType($property, static::$types[$type]);
} else {
$key = strtolower($context->fullyQualifiedName($type));
if ($property->ref === UNDEFINED && array_key_exists($key, $refs)) {
if ($property->ref === Generator::UNDEFINED && array_key_exists($key, $refs)) {
$this->applyRef($property, $refs[$key]);
continue;
}
}
} elseif (preg_match('/@var\s+(?<type>[^\s]+)([ \t])?(?<description>.+)?$/im', $comment, $varMatches)) {
if ($property->type === UNDEFINED) {
if ($property->type === Generator::UNDEFINED) {
preg_match('/^([^\[]+)(.*$)/', trim($varMatches['type']), $typeMatches);
$isNullable = $this->isNullable($typeMatches[1]);
$type = $this->stripNull($typeMatches[1]);
if (array_key_exists(strtolower($type), static::$types) === false) {
$key = strtolower($context->fullyQualifiedName($type));
if ($property->ref === UNDEFINED && $typeMatches[2] === '' && array_key_exists($key, $refs)) {
if ($property->ref === Generator::UNDEFINED && $typeMatches[2] === '' && array_key_exists($key, $refs)) {
if ($isNullable) {
$property->oneOf = [
new Schema([
@ -102,7 +103,7 @@ class AugmentProperties
} else {
$type = static::$types[strtolower($type)];
if (is_array($type)) {
if ($property->format === UNDEFINED) {
if ($property->format === Generator::UNDEFINED) {
$property->format = $type[1];
}
$type = $type[0];
@ -110,34 +111,34 @@ class AugmentProperties
$property->type = $type;
}
if ($typeMatches[2] === '[]') {
if ($property->items === UNDEFINED) {
if ($property->items === Generator::UNDEFINED) {
$property->items = new Items(
[
'type' => $property->type,
'_context' => new Context(['generated' => true], $context),
]
);
if ($property->items->type === UNDEFINED) {
if ($property->items->type === Generator::UNDEFINED) {
$key = strtolower($context->fullyQualifiedName($type));
$property->items->ref = array_key_exists($key, $refs) ? $refs[$key] : null;
}
}
$property->type = 'array';
}
if ($isNullable && $property->nullable === UNDEFINED) {
if ($isNullable && $property->nullable === Generator::UNDEFINED) {
$property->nullable = true;
}
}
if ($property->description === UNDEFINED && isset($varMatches['description'])) {
if ($property->description === Generator::UNDEFINED && isset($varMatches['description'])) {
$property->description = trim($varMatches['description']);
}
}
if ($property->example === UNDEFINED && preg_match('/@example\s+([ \t])?(?<example>.+)?$/im', $comment, $varMatches)) {
if ($property->example === Generator::UNDEFINED && preg_match('/@example\s+([ \t])?(?<example>.+)?$/im', $comment, $varMatches)) {
$property->example = $varMatches['example'];
}
if ($property->description === UNDEFINED) {
if ($property->description === Generator::UNDEFINED) {
$property->description = $context->phpdocContent();
}
}
@ -167,7 +168,7 @@ class AugmentProperties
protected function applyType(Property $property, $type): void
{
if (is_array($type)) {
if ($property->format === UNDEFINED) {
if ($property->format === Generator::UNDEFINED) {
$property->format = $type[1];
}
$type = $type[0];

View File

@ -9,6 +9,7 @@ namespace OpenApi\Processors;
use OpenApi\Analysis;
use OpenApi\Annotations\Property;
use OpenApi\Annotations\Schema;
use OpenApi\Generator;
/**
* Use the Schema context to extract useful information and inject that into the annotation.
@ -22,7 +23,7 @@ class AugmentSchemas
$schemas = $analysis->getAnnotationsOfType(Schema::class);
// Use the class names for @OA\Schema()
foreach ($schemas as $schema) {
if ($schema->schema === UNDEFINED) {
if ($schema->schema === Generator::UNDEFINED) {
if ($schema->_context->is('class')) {
$schema->schema = $schema->_context->class;
} elseif ($schema->_context->is('interface')) {
@ -48,10 +49,10 @@ class AugmentSchemas
continue;
}
if ($annotation->allOf !== UNDEFINED) {
if ($annotation->allOf !== Generator::UNDEFINED) {
$schema = null;
foreach ($annotation->allOf as $nestedSchema) {
if ($nestedSchema->ref !== UNDEFINED) {
if ($nestedSchema->ref !== Generator::UNDEFINED) {
continue;
}
@ -76,7 +77,7 @@ class AugmentSchemas
// set schema type based on various properties
foreach ($schemas as $schema) {
if ($schema->type === UNDEFINED) {
if ($schema->type === Generator::UNDEFINED) {
if (is_array($schema->properties) && count($schema->properties) > 0) {
$schema->type = 'object';
} elseif (is_array($schema->additionalProperties) && count($schema->additionalProperties) > 0) {
@ -91,10 +92,10 @@ class AugmentSchemas
// move schema properties into allOf if both exist
foreach ($schemas as $schema) {
if ($schema->properties !== UNDEFINED and $schema->allOf !== UNDEFINED) {
if ($schema->properties !== Generator::UNDEFINED and $schema->allOf !== Generator::UNDEFINED) {
$allOfPropertiesSchema = null;
foreach ($schema->allOf as $allOfSchema) {
if ($allOfSchema->ref === UNDEFINED) {
if ($allOfSchema->ref === Generator::UNDEFINED) {
$allOfPropertiesSchema = $allOfSchema;
break;
}
@ -104,7 +105,7 @@ class AugmentSchemas
$schema->allOf[] = $allOfPropertiesSchema;
}
$allOfPropertiesSchema->properties = array_merge($allOfPropertiesSchema->properties, $schema->properties);
$schema->properties = UNDEFINED;
$schema->properties = Generator::UNDEFINED;
}
}
}

View File

@ -10,6 +10,7 @@ use OpenApi\Analysis;
use OpenApi\Annotations\Operation;
use OpenApi\Annotations\PathItem;
use OpenApi\Context;
use OpenApi\Generator;
use OpenApi\Logger;
/**
@ -21,7 +22,7 @@ class BuildPaths
{
$paths = [];
// Merge @OA\PathItems with the same path.
if ($analysis->openapi->paths !== UNDEFINED) {
if ($analysis->openapi->paths !== Generator::UNDEFINED) {
foreach ($analysis->openapi->paths as $annotation) {
if (empty($annotation->path)) {
Logger::notice($annotation->identity().' is missing required property "path" in '.$annotation->_context);

View File

@ -7,6 +7,7 @@
namespace OpenApi\Processors;
use OpenApi\Analysis;
use OpenApi\Generator;
/**
* This would be detected as summary.
@ -49,9 +50,9 @@ class DocBlockDescriptions
private function description($annotation): void
{
if ($annotation->description !== UNDEFINED) {
if ($annotation->description !== Generator::UNDEFINED) {
if ($annotation->description === null) {
$annotation->description = UNDEFINED;
$annotation->description = Generator::UNDEFINED;
}
return;
@ -61,14 +62,14 @@ class DocBlockDescriptions
private function summaryAndDescription($annotation): void
{
$ignoreSummary = $annotation->summary !== UNDEFINED;
$ignoreDescription = $annotation->description !== UNDEFINED;
$ignoreSummary = $annotation->summary !== Generator::UNDEFINED;
$ignoreDescription = $annotation->description !== Generator::UNDEFINED;
if ($annotation->summary === null) {
$ignoreSummary = true;
$annotation->summary = UNDEFINED;
$annotation->summary = Generator::UNDEFINED;
}
if ($annotation->description === null) {
$annotation->description = UNDEFINED;
$annotation->description = Generator::UNDEFINED;
$ignoreDescription = true;
}
if ($ignoreSummary && $ignoreDescription) {

View File

@ -10,6 +10,7 @@ use OpenApi\Analysis;
use OpenApi\Annotations\Components;
use OpenApi\Annotations\Property;
use OpenApi\Annotations\Schema;
use OpenApi\Generator;
use OpenApi\Util;
class ExpandInterfaces
@ -35,10 +36,10 @@ class ExpandInterfaces
protected function inheritInterface(Schema $schema, array $interface, Schema $interfaceSchema): void
{
if ($schema->allOf === UNDEFINED) {
if ($schema->allOf === Generator::UNDEFINED) {
$schema->allOf = [];
}
$refPath = $interfaceSchema->schema !== UNDEFINED ? $interfaceSchema->schema : $interface['interface'];
$refPath = $interfaceSchema->schema !== Generator::UNDEFINED ? $interfaceSchema->schema : $interface['interface'];
$schema->allOf[] = new Schema([
'_context' => $interface['context']->_context,
'ref' => Components::SCHEMA_REF.Util::refEncode($refPath),

View File

@ -10,7 +10,7 @@ use OpenApi\Analysis;
use OpenApi\Annotations\Components;
use OpenApi\Annotations\Property;
use OpenApi\Annotations\Schema;
use const OpenApi\UNDEFINED;
use OpenApi\Generator;
use OpenApi\Util;
use Traversable;
@ -40,8 +40,8 @@ class ExpandTraits
protected function inheritTrait(Schema $schema, array $trait, Schema $traitSchema): void
{
$refPath = $traitSchema->schema !== UNDEFINED ? $traitSchema->schema : $trait['trait'];
if ($schema->allOf === UNDEFINED) {
$refPath = $traitSchema->schema !== Generator::UNDEFINED ? $traitSchema->schema : $trait['trait'];
if ($schema->allOf === Generator::UNDEFINED) {
$schema->allOf = [];
}
$schema->allOf[] = new Schema([

View File

@ -11,7 +11,7 @@ use OpenApi\Annotations\Components;
use OpenApi\Annotations\Property;
use OpenApi\Annotations\Schema;
use OpenApi\Context;
use const OpenApi\UNDEFINED;
use OpenApi\Generator;
use OpenApi\Util;
use Traversable;
@ -47,7 +47,7 @@ class InheritProperties
foreach ($classes as $class) {
if ($class['context']->annotations) {
foreach ($class['context']->annotations as $annotation) {
if ($annotation instanceof Schema && $annotation->schema !== UNDEFINED) {
if ($annotation instanceof Schema && $annotation->schema !== Generator::UNDEFINED) {
$this->inherit($schema, $annotation);
continue 2;
@ -76,7 +76,7 @@ class InheritProperties
*/
private function inherit(Schema $to, Schema $from): void
{
if ($to->allOf === UNDEFINED) {
if ($to->allOf === Generator::UNDEFINED) {
// Move all properties into an `allOf` entry except the `schema` property.
$clone = new Schema(['_context' => new Context(['generated' => true], $to->_context)]);
$clone->mergeProperties($to);
@ -84,7 +84,7 @@ class InheritProperties
$defaultValues = get_class_vars(Schema::class);
foreach (array_keys(get_object_vars($clone)) as $property) {
if (in_array($property, ['schema', 'title', 'description'])) {
$clone->$property = UNDEFINED;
$clone->$property = Generator::UNDEFINED;
continue;
}
if ($to->$property !== $defaultValues[$property]) {
@ -99,7 +99,7 @@ class InheritProperties
}
$append = true;
foreach ($to->allOf as $entry) {
if ($entry->ref !== UNDEFINED && $entry->ref === Components::SCHEMA_REF.Util::refEncode($from->schema)) {
if ($entry->ref !== Generator::UNDEFINED && $entry->ref === Components::SCHEMA_REF.Util::refEncode($from->schema)) {
$append = false; // ref was already specified manualy
}
}

View File

@ -9,7 +9,7 @@ namespace OpenApi\Processors;
use OpenApi\Analysis;
use OpenApi\Annotations\Components;
use OpenApi\Annotations\Schema;
use const OpenApi\UNDEFINED;
use OpenApi\Generator;
use OpenApi\Util;
class InheritTraits
@ -24,8 +24,8 @@ class InheritTraits
foreach ($traits as $trait) {
$traitSchema = $analysis->getSchemaForSource($trait['context']->fullyQualifiedName($trait['trait']));
if ($traitSchema) {
$refPath = $traitSchema->schema !== UNDEFINED ? $traitSchema->schema : $trait['trait'];
if ($schema->allOf === UNDEFINED) {
$refPath = $traitSchema->schema !== Generator::UNDEFINED ? $traitSchema->schema : $trait['trait'];
if ($schema->allOf === Generator::UNDEFINED) {
$schema->allOf = [];
}
$schema->allOf[] = new Schema([

View File

@ -8,7 +8,7 @@ namespace OpenApi\Processors;
use OpenApi\Analysis;
use OpenApi\Annotations\Components;
use OpenApi\UNDEFINED;
use OpenApi\Generator;
/**
* Merge reusable annotation into @OA\Schemas.
@ -18,7 +18,7 @@ class MergeIntoComponents
public function __invoke(Analysis $analysis)
{
$components = $analysis->openapi->components;
if ($components === UNDEFINED) {
if ($components === Generator::UNDEFINED) {
$components = new Components([]);
$components->_context->generated = true;
}

View File

@ -35,9 +35,9 @@ class MergeIntoOpenApi
$paths = $annotation->paths;
unset($annotation->paths);
$openapi->mergeProperties($annotation);
if ($paths !== UNDEFINED) {
if ($paths !== Generator::UNDEFINED) {
foreach ($paths as $path) {
if ($openapi->paths === UNDEFINED) {
if ($openapi->paths === Generator::UNDEFINED) {
$openapi->paths = [];
}
$openapi->paths[] = $path;

View File

@ -13,6 +13,7 @@ use OpenApi\Annotations\Parameter;
use OpenApi\Annotations\RequestBody;
use OpenApi\Annotations\Response;
use OpenApi\Context;
use OpenApi\Generator;
use OpenApi\Logger;
/**
@ -33,7 +34,7 @@ class MergeJsonContent
}
continue;
}
if ($parent->content === UNDEFINED) {
if ($parent->content === Generator::UNDEFINED) {
$parent->content = [];
}
$parent->content['application/json'] = new MediaType([
@ -45,8 +46,8 @@ class MergeJsonContent
if (!$parent instanceof Parameter) {
$parent->content['application/json']->mediaType = 'application/json';
}
$jsonContent->example = UNDEFINED;
$jsonContent->examples = UNDEFINED;
$jsonContent->example = Generator::UNDEFINED;
$jsonContent->examples = Generator::UNDEFINED;
$index = array_search($jsonContent, $parent->_unmerged, true);
if ($index !== false) {

View File

@ -13,6 +13,7 @@ use OpenApi\Annotations\RequestBody;
use OpenApi\Annotations\Response;
use OpenApi\Annotations\XmlContent;
use OpenApi\Context;
use OpenApi\Generator;
use OpenApi\Logger;
/**
@ -33,7 +34,7 @@ class MergeXmlContent
}
continue;
}
if ($parent->content === UNDEFINED) {
if ($parent->content === Generator::UNDEFINED) {
$parent->content = [];
}
$parent->content['application/xml'] = new MediaType([
@ -45,8 +46,8 @@ class MergeXmlContent
if (!$parent instanceof Parameter) {
$parent->content['application/xml']->mediaType = 'application/xml';
}
$xmlContent->example = UNDEFINED;
$xmlContent->examples = UNDEFINED;
$xmlContent->example = Generator::UNDEFINED;
$xmlContent->examples = Generator::UNDEFINED;
$index = array_search($xmlContent, $parent->_unmerged, true);
if ($index !== false) {

View File

@ -8,6 +8,7 @@ namespace OpenApi\Processors;
use OpenApi\Analysis;
use OpenApi\Annotations\Operation;
use OpenApi\Generator;
/**
* Generate the OperationId based on the context of the OpenApi annotation.
@ -19,7 +20,7 @@ class OperationId
$allOperations = $analysis->getAnnotationsOfType(Operation::class);
foreach ($allOperations as $operation) {
if ($operation->operationId !== UNDEFINED) {
if ($operation->operationId !== Generator::UNDEFINED) {
continue;
}
$context = $operation->_context;

View File

@ -112,7 +112,7 @@ class Serializer
}
if (substr($property, 0, 2) === 'x-') {
if ($annotation->x === UNDEFINED) {
if ($annotation->x === Generator::UNDEFINED) {
$annotation->x = [];
}
$custom = substr($property, 2);

View File

@ -82,7 +82,7 @@ class StaticAnalyser
continue;
}
if ($token[0] === T_ATTRIBUTE) {
if (defined('T_ATTRIBUTE') && $token[0] === T_ATTRIBUTE) {
// consume
$this->parseAttribute($tokens, $token, $parseContext);
continue;
@ -445,15 +445,21 @@ class StaticAnalyser
}
}
private function php8NamespaceToken()
{
return defined('T_NAME_QUALIFIED') ? [T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED] : [];
}
/**
* Parse namespaced string.
*/
private function parseNamespace(array &$tokens, &$token, Context $parseContext): string
{
$namespace = '';
$nsToken = array_merge([T_STRING, T_NS_SEPARATOR], $this->php8NamespaceToken());
while ($token !== false) {
$token = $this->nextToken($tokens, $parseContext);
if (!in_array($token[0], [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED])) {
if (!in_array($token[0], $nsToken)) {
break;
}
$namespace .= $token[1];
@ -494,9 +500,10 @@ class StaticAnalyser
$alias = '';
$statements = [];
$explicitAlias = false;
$nsToken = array_merge([T_STRING, T_NS_SEPARATOR], $this->php8NamespaceToken());
while ($token !== false) {
$token = $this->nextToken($tokens, $parseContext);
$isNameToken = in_array($token[0], [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]);
$isNameToken = in_array($token[0], $nsToken);
if (!$explicitAlias && $isNameToken) {
$class .= $token[1];
$alias = $token[1];
@ -526,7 +533,7 @@ class StaticAnalyser
*/
private function parseTypeAndNextToken(array &$tokens, Context $parseContext): array
{
$type = UNDEFINED;
$type = Generator::UNDEFINED;
$nullable = false;
$token = $this->nextToken($tokens, $parseContext);
@ -539,9 +546,11 @@ class StaticAnalyser
$token = $this->nextToken($tokens, $parseContext);
}
$qualifiedToken = array_merge([T_NS_SEPARATOR, T_STRING, T_ARRAY], $this->php8NamespaceToken());
$typeToken = array_merge([T_STRING], $this->php8NamespaceToken());
// drill down namespace segments to basename property type declaration
while (in_array($token[0], [T_NS_SEPARATOR, T_STRING, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED, T_ARRAY])) {
if (in_array($token[0], [T_STRING, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED])) {
while (in_array($token[0], $qualifiedToken)) {
if (in_array($token[0], $typeToken)) {
$type = $token[1];
}
$token = $this->nextToken($tokens, $parseContext);

View File

@ -12,21 +12,24 @@ use Symfony\Component\Finder\Finder;
if (defined('OpenApi\\UNDEFINED') === false) {
/*
* Special value to differentiate between null and undefined.
*
* @deprecated use Generator::UNDEFINED
*/
define('OpenApi\\UNDEFINED', '@OA\\UNDEFINED🙈');
define('OpenApi\\Annotations\\UNDEFINED', UNDEFINED);
define('OpenApi\\Processors\\UNDEFINED', UNDEFINED);
}
define('OpenApi\\UNDEFINED', Generator::UNDEFINED);
// PHP 8.0
if (!defined('T_NAME_QUALIFIED')) {
define('T_NAME_QUALIFIED', -4);
}
if (!defined('T_NAME_FULLY_QUALIFIED')) {
define('T_NAME_FULLY_QUALIFIED', -5);
}
if (!defined('T_ATTRIBUTE')) {
define('T_ATTRIBUTE', -6);
/*
* Special value to differentiate between null and undefined.
*
* @deprecated use Generator::UNDEFINED
*/
define('OpenApi\\Annotations\\UNDEFINED', Generator::UNDEFINED);
/*
* Special value to differentiate between null and undefined.
*
* @deprecated use Generator::UNDEFINED
*/
define('OpenApi\\Processors\\UNDEFINED', Generator::UNDEFINED);
}
if (!function_exists('OpenApi\\scan')) {
@ -42,25 +45,14 @@ if (!function_exists('OpenApi\\scan')) {
* processors: defaults to the registered processors in Analysis
*
* @return OpenApi
*
* @deprecated use \OpenApi\Generator instead
*/
function scan($directory, $options = [])
{
$analyser = array_key_exists('analyser', $options) ? $options['analyser'] : new StaticAnalyser();
$analysis = array_key_exists('analysis', $options) ? $options['analysis'] : new Analysis();
$processors = array_key_exists('processors', $options) ? $options['processors'] : Analysis::processors();
$exclude = array_key_exists('exclude', $options) ? $options['exclude'] : null;
$pattern = array_key_exists('pattern', $options) ? $options['pattern'] : null;
// Crawl directory and parse all files
$finder = Util::finder($directory, $exclude, $pattern);
foreach ($finder as $file) {
$analysis->addAnalysis($analyser->fromFile($file->getPathname()));
}
// Post processing
$analysis->process($processors);
// Validation (Generate notices & warnings)
$analysis->validate();
return $analysis->openapi;
return Generator::scan(Util::finder($directory, $exclude, $pattern), $options);
}
}

View File

@ -6,7 +6,7 @@
namespace OpenApi\Tests\Annotations;
use function get_class_vars;
use function \get_class_vars;
use OpenApi\Annotations\AbstractAnnotation;
use OpenApi\Tests\OpenApiTestCase;

View File

@ -6,12 +6,12 @@
namespace OpenApi\Tests\Annotations;
use OpenApi\Generator;
use OpenApi\Processors\AugmentProperties;
use OpenApi\Processors\AugmentSchemas;
use OpenApi\Processors\MergeIntoComponents;
use OpenApi\Processors\MergeIntoOpenApi;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
class NestedPropertyTest extends OpenApiTestCase
{
@ -43,6 +43,6 @@ class NestedPropertyTest extends OpenApiTestCase
// verbose not-recommend notations
$theBabyOfBabyBaby = $theBabyOfBaby->properties[0];
$this->assertEquals('theBabyOfBabyBaby', $theBabyOfBabyBaby->property);
$this->assertSame(UNDEFINED, $theBabyOfBabyBaby->properties);
$this->assertSame(Generator::UNDEFINED, $theBabyOfBabyBaby->properties);
}
}

View File

@ -7,6 +7,7 @@
namespace OpenApi\Tests;
use OpenApi\Context;
use OpenApi\Generator;
class ContextTest extends OpenApiTestCase
{
@ -25,7 +26,7 @@ class ContextTest extends OpenApiTestCase
public function testFullyQualifiedName()
{
$this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found');
$openapi = \OpenApi\scan(__DIR__.'/Fixtures/Customer.php');
$openapi = Generator::scan([__DIR__.'/Fixtures/Customer.php']);
$context = $openapi->components->schemas[0]->_context;
// resolve with namespace
$this->assertSame('\FullyQualified', $context->fullyQualifiedName('\FullyQualified'));

View File

@ -6,6 +6,8 @@
namespace OpenApi\Tests;
use OpenApi\Generator;
class ExamplesTest extends OpenApiTestCase
{
public function exampleMappings()
@ -33,7 +35,7 @@ class ExamplesTest extends OpenApiTestCase
public function testExamples($example, $spec)
{
$path = __DIR__.'/../Examples/'.$example;
$openapi = \OpenApi\scan($path, []);
$openapi = Generator::scan([$path]);
$this->assertSpecEquals(file_get_contents($path.'/'.$spec), $openapi, 'Examples/'.$example.'/'.$spec);
}
}

View File

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace OpenApi\Tests\Fixtures;
use OpenApi\Annotations as OA;
/**
* @SWG\Definition()
*/
class Deprecated
{
}

60
tests/GeneratorTest.php Normal file
View File

@ -0,0 +1,60 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Tests;
use OpenApi\Generator;
use OpenApi\Logger;
use OpenApi\Util;
class GeneratorTest extends OpenApiTestCase
{
const SOURCE_DIR = __DIR__.'/../Examples/swagger-spec/petstore-simple';
public function sourcesProvider()
{
$sourceDir = self::SOURCE_DIR;
$sources = [
$sourceDir.'/SimplePet.php',
$sourceDir.'/SimplePetsController.php',
$sourceDir.'/api.php',
];
return [
'dir-list' => [$sourceDir, [$sourceDir]],
'file-list' => [$sourceDir, $sources],
'finder' => [$sourceDir, Util::finder($sourceDir)],
'finder-list' => [$sourceDir, [Util::finder($sourceDir)]],
];
}
/**
* @dataProvider sourcesProvider
*/
public function testScan(string $sourceDir, iterable $sources)
{
$openapi = (new Generator())
->scan($sources);
$this->assertSpecEquals(file_get_contents(sprintf('%s/%s.yaml', $sourceDir, basename($sourceDir))), $openapi);
}
public function testLogger()
{
// reset test logger
Logger::getInstance()->log = function ($entry, $type) {
$this->fail('Wrong logger');
};
$this->assertOpenApiLogEntryContains('The annotation @SWG\Definition() is deprecated.');
$this->assertOpenApiLogEntryContains('Required @OA\Info() not found');
$this->assertOpenApiLogEntryContains('Required @OA\PathItem() not found');
(new Generator($this->getPsrLogger(true)))
->setAliases(['swg' => 'OpenApi\Annotations'])
->generate($this->fixtures('Deprecated.php'));
}
}

View File

@ -19,6 +19,9 @@ use OpenApi\Context;
use OpenApi\Logger;
use OpenApi\StaticAnalyser;
use PHPUnit\Framework\TestCase;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
@ -27,7 +30,7 @@ class OpenApiTestCase extends TestCase
/**
* @var array
*/
private $expectedLogMessages;
public $expectedLogMessages = [];
/**
* @var Closure
@ -72,6 +75,40 @@ class OpenApiTestCase extends TestCase
parent::tearDown();
}
public function getPsrLogger(bool $tracking = false): ?LoggerInterface
{
if (!$tracking) {
// allow to test the default behaviour without injected PSR logger
switch (strtoupper($_ENV['NON_TRACKING_LOGGER'] ?? 'FALLBACK')) {
case 'NULL':
return new NullLogger();
case 'FALLBACK':
default:
// whatever is set up in Logger::$instance->log
return null;
}
}
return new class($this) extends AbstractLogger {
protected $testCase;
public function __construct($testCase)
{
$this->testCase = $testCase;
}
public function log($level, $message, array $context = [])
{
if (count($this->testCase->expectedLogMessages)) {
list($assertion, $needle) = array_shift($this->testCase->expectedLogMessages);
$assertion($message, $level);
} else {
$this->testCase->fail('Unexpected \OpenApi\Logger::'.$level.'("'.$message.'")');
}
}
};
}
public function assertOpenApiLogEntryContains($needle, $message = '')
{
$this->expectedLogMessages[] = [function ($entry, $type) use ($needle, $message) {
@ -231,7 +268,7 @@ class OpenApiTestCase extends TestCase
if (in_array($class, ['AbstractAnnotation', 'Operation'])) {
continue;
}
$classes[] = ['OpenApi\\Annotations\\'.$class];
$classes[$class] = ['OpenApi\\Annotations\\'.$class];
}
return $classes;

View File

@ -6,13 +6,14 @@
namespace OpenApi\Tests\Processors;
use OpenApi\Generator;
use OpenApi\Tests\OpenApiTestCase;
class AugmentParameterTest extends OpenApiTestCase
{
public function testAugmentParameter()
{
$openapi = \OpenApi\scan($this->fixtures('UsingRefs.php'));
$openapi = Generator::scan($this->fixtures('UsingRefs.php'));
$this->assertCount(1, $openapi->components->parameters, 'OpenApi contains 1 reusable parameter specification');
$this->assertEquals('ItemName', $openapi->components->parameters[0]->parameter, 'When no @OA\Parameter()->parameter is specified, use @OA\Parameter()->name');
}

View File

@ -7,12 +7,12 @@
namespace OpenApi\Tests\Processors;
use OpenApi\Annotations\Property;
use OpenApi\Generator;
use OpenApi\Processors\AugmentProperties;
use OpenApi\Processors\AugmentSchemas;
use OpenApi\Processors\MergeIntoComponents;
use OpenApi\Processors\MergeIntoOpenApi;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
/**
* @group Properties
@ -37,27 +37,27 @@ class AugmentPropertiesTest extends OpenApiTestCase
$bestFriend = $customer->properties[8];
// Verify no values where defined in the annotation.
$this->assertSame(UNDEFINED, $firstName->property);
$this->assertSame(UNDEFINED, $firstName->description);
$this->assertSame(UNDEFINED, $firstName->type);
$this->assertSame(Generator::UNDEFINED, $firstName->property);
$this->assertSame(Generator::UNDEFINED, $firstName->description);
$this->assertSame(Generator::UNDEFINED, $firstName->type);
$this->assertSame(UNDEFINED, $lastName->property);
$this->assertSame(UNDEFINED, $lastName->description);
$this->assertSame(UNDEFINED, $lastName->type);
$this->assertSame(Generator::UNDEFINED, $lastName->property);
$this->assertSame(Generator::UNDEFINED, $lastName->description);
$this->assertSame(Generator::UNDEFINED, $lastName->type);
$this->assertSame(UNDEFINED, $tags->property);
$this->assertSame(UNDEFINED, $tags->type);
$this->assertSame(UNDEFINED, $tags->items);
$this->assertSame(Generator::UNDEFINED, $tags->property);
$this->assertSame(Generator::UNDEFINED, $tags->type);
$this->assertSame(Generator::UNDEFINED, $tags->items);
$this->assertSame(UNDEFINED, $submittedBy->property);
$this->assertSame(UNDEFINED, $submittedBy->ref);
$this->assertSame(Generator::UNDEFINED, $submittedBy->property);
$this->assertSame(Generator::UNDEFINED, $submittedBy->ref);
$this->assertSame(UNDEFINED, $friends->property);
$this->assertSame(UNDEFINED, $friends->type);
$this->assertSame(Generator::UNDEFINED, $friends->property);
$this->assertSame(Generator::UNDEFINED, $friends->type);
$this->assertSame(UNDEFINED, $bestFriend->property);
$this->assertSame(UNDEFINED, $bestFriend->nullable);
$this->assertSame(UNDEFINED, $bestFriend->allOf);
$this->assertSame(Generator::UNDEFINED, $bestFriend->property);
$this->assertSame(Generator::UNDEFINED, $bestFriend->nullable);
$this->assertSame(Generator::UNDEFINED, $bestFriend->allOf);
$analysis->process(new AugmentProperties());
@ -91,14 +91,14 @@ class AugmentPropertiesTest extends OpenApiTestCase
'property' => 'fourthname',
'example' => 'Unknown',
'description' => 'The unknown name of the customer.',
'type' => UNDEFINED,
'type' => Generator::UNDEFINED,
'nullable' => true,
];
$this->assertName($fourthName, $expectedValues);
$expectedValues = [
'property' => 'lastname',
'example' => UNDEFINED,
'example' => Generator::UNDEFINED,
'description' => 'The lastname of the customer.',
'type' => 'string',
];
@ -147,72 +147,72 @@ class AugmentPropertiesTest extends OpenApiTestCase
] = $analysis->openapi->components->schemas[0]->properties;
$this->assertName($stringType, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($intType, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($nullableString, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($arrayType, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($dateTime, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($qualified, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($namespaced, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($importedNamespace, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($nativeTrumpsVar, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($annotationTrumpsNative, [
'property' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => 'integer',
]);
$this->assertName($annotationTrumpsAll, [
'property' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => 'integer',
]);
$this->assertName($undefined, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($onlyAnnotated, [
'property' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => 'integer',
]);
$this->assertName($onlyVar, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($staticUndefined, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($staticString, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($staticNullableString, [
'property' => UNDEFINED,
'type' => UNDEFINED,
'property' => Generator::UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$analysis->process(new AugmentProperties());
@ -273,7 +273,7 @@ class AugmentPropertiesTest extends OpenApiTestCase
]);
$this->assertName($undefined, [
'property' => 'undefined',
'type' => UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($onlyAnnotated, [
'property' => 'onlyAnnotated',
@ -285,7 +285,7 @@ class AugmentPropertiesTest extends OpenApiTestCase
]);
$this->assertName($staticUndefined, [
'property' => 'staticUndefined',
'type' => UNDEFINED,
'type' => Generator::UNDEFINED,
]);
$this->assertName($staticString, [
'property' => 'staticString',

View File

@ -6,11 +6,11 @@
namespace OpenApi\Tests\Processors;
use OpenApi\Generator;
use OpenApi\Processors\AugmentSchemas;
use OpenApi\Processors\MergeIntoComponents;
use OpenApi\Processors\MergeIntoOpenApi;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
class AugmentSchemasTest extends OpenApiTestCase
{
@ -22,8 +22,8 @@ class AugmentSchemasTest extends OpenApiTestCase
$this->assertCount(1, $analysis->openapi->components->schemas);
$customer = $analysis->openapi->components->schemas[0];
$this->assertSame(UNDEFINED, $customer->schema, 'Sanity check. No scheme was defined');
$this->assertSame(UNDEFINED, $customer->properties, 'Sanity check. @OA\Property\'s not yet merged ');
$this->assertSame(Generator::UNDEFINED, $customer->schema, 'Sanity check. No scheme was defined');
$this->assertSame(Generator::UNDEFINED, $customer->properties, 'Sanity check. @OA\Property\'s not yet merged ');
$analysis->process(new AugmentSchemas());
$this->assertSame('Customer', $customer->schema, '@OA\Schema()->schema based on classname');
$this->assertCount(9, $customer->properties, '@OA\Property()s are merged into the @OA\Schema of the class');
@ -37,7 +37,7 @@ class AugmentSchemasTest extends OpenApiTestCase
$this->assertCount(1, $analysis->openapi->components->schemas);
$customer = $analysis->openapi->components->schemas[0];
$this->assertSame(UNDEFINED, $customer->properties, 'Sanity check. @OA\Property\'s not yet merged ');
$this->assertSame(Generator::UNDEFINED, $customer->properties, 'Sanity check. @OA\Property\'s not yet merged ');
$analysis->process(new AugmentSchemas());
$this->assertCount(9, $customer->properties, '@OA\Property()s are merged into the @OA\Schema of the class');
}

View File

@ -11,10 +11,10 @@ use OpenApi\Annotations\Get;
use OpenApi\Annotations\OpenApi;
use OpenApi\Annotations\PathItem;
use OpenApi\Annotations\Post;
use OpenApi\Generator;
use OpenApi\Processors\BuildPaths;
use OpenApi\Processors\MergeIntoOpenApi;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
class BuildPathsTest extends OpenApiTestCase
{
@ -50,6 +50,6 @@ class BuildPathsTest extends OpenApiTestCase
$this->assertInstanceOf(PathItem::class, $path);
$this->assertInstanceOf(Get::class, $path->get);
$this->assertInstanceOf(Post::class, $path->post);
$this->assertSame(UNDEFINED, $path->put);
$this->assertSame(Generator::UNDEFINED, $path->put);
}
}

View File

@ -7,9 +7,9 @@
namespace OpenApi\Tests\Processors;
use OpenApi\Annotations\Operation;
use OpenApi\Generator;
use OpenApi\Processors\DocBlockDescriptions;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
class DocBlockDescriptionsTest extends OpenApiTestCase
{
@ -29,6 +29,6 @@ class DocBlockDescriptionsTest extends OpenApiTestCase
$this->assertSame('api/test2', $operations[1]->path);
$this->assertSame('Example summary', $operations[1]->summary, 'Operation summary should be taken from phpDoc');
$this->assertSame(UNDEFINED, $operations[1]->description, 'This operation only has summary in the phpDoc, no description');
$this->assertSame(Generator::UNDEFINED, $operations[1]->description, 'This operation only has summary in the phpDoc, no description');
}
}

View File

@ -11,6 +11,7 @@ use OpenApi\Annotations\Components;
use OpenApi\Annotations\Info;
use OpenApi\Annotations\PathItem;
use OpenApi\Annotations\Schema;
use OpenApi\Generator;
use OpenApi\Processors\AugmentProperties;
use OpenApi\Processors\AugmentSchemas;
use OpenApi\Processors\BuildPaths;
@ -21,7 +22,6 @@ use OpenApi\Processors\InheritTraits;
use OpenApi\Processors\MergeIntoComponents;
use OpenApi\Processors\MergeIntoOpenApi;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
class InheritPropertiesTest extends OpenApiTestCase
{
@ -130,14 +130,14 @@ class InheritPropertiesTest extends OpenApiTestCase
/* @var Schema $extendedSchema */
$extendedSchema = $schemas[0];
$this->assertSame('ExtendedModel', $extendedSchema->schema);
$this->assertSame(UNDEFINED, $extendedSchema->properties);
$this->assertSame(Generator::UNDEFINED, $extendedSchema->properties);
$this->assertArrayHasKey(1, $extendedSchema->allOf);
$this->assertEquals($extendedSchema->allOf[1]->properties[0]->property, 'extendedProperty');
/* @var $includeSchemaWithRef Schema */
$includeSchemaWithRef = $schemas[1];
$this->assertSame(UNDEFINED, $includeSchemaWithRef->properties);
$this->assertSame(Generator::UNDEFINED, $includeSchemaWithRef->properties);
}
/**
@ -169,7 +169,7 @@ class InheritPropertiesTest extends OpenApiTestCase
/* @var Schema $extendedSchema */
$extendedSchema = $schemas[0];
$this->assertSame('ExtendedWithoutAllOf', $extendedSchema->schema);
$this->assertSame(UNDEFINED, $extendedSchema->properties);
$this->assertSame(Generator::UNDEFINED, $extendedSchema->properties);
$this->assertCount(2, $extendedSchema->allOf);
@ -206,7 +206,7 @@ class InheritPropertiesTest extends OpenApiTestCase
/* @var Schema $extendedSchema */
$extendedSchema = $schemas[0];
$this->assertSame('ExtendedWithTwoSchemas', $extendedSchema->schema);
$this->assertSame(UNDEFINED, $extendedSchema->properties);
$this->assertSame(Generator::UNDEFINED, $extendedSchema->properties);
$this->assertCount(2, $extendedSchema->allOf);
$this->assertEquals($extendedSchema->allOf[0]->ref, Components::SCHEMA_REF.'Base');
@ -215,7 +215,7 @@ class InheritPropertiesTest extends OpenApiTestCase
/* @var $nestedSchema Schema */
$nestedSchema = $schemas[1];
$this->assertSame(UNDEFINED, $nestedSchema->allOf);
$this->assertSame(Generator::UNDEFINED, $nestedSchema->allOf);
$this->assertCount(1, $nestedSchema->properties);
$this->assertEquals($nestedSchema->properties[0]->property, 'nestedProperty');
}
@ -255,24 +255,24 @@ class InheritPropertiesTest extends OpenApiTestCase
$baseInterface = $schemas[0];
$this->assertSame('BaseInterface', $baseInterface->schema);
$this->assertEquals($baseInterface->properties[0]->property, 'interfaceProperty');
$this->assertEquals(UNDEFINED, $baseInterface->allOf);
$this->assertEquals(Generator::UNDEFINED, $baseInterface->allOf);
$extendsBaseThatImplements = $schemas[1];
$this->assertSame('ExtendsBaseThatImplements', $extendsBaseThatImplements->schema);
$this->assertEquals(UNDEFINED, $extendsBaseThatImplements->properties);
$this->assertNotEquals(UNDEFINED, $extendsBaseThatImplements->allOf);
$this->assertEquals(Generator::UNDEFINED, $extendsBaseThatImplements->properties);
$this->assertNotEquals(Generator::UNDEFINED, $extendsBaseThatImplements->allOf);
// base, trait and own properties
$this->assertCount(3, $extendsBaseThatImplements->allOf);
$baseThatImplements = $schemas[2];
$this->assertSame('BaseThatImplements', $baseThatImplements->schema);
$this->assertEquals(UNDEFINED, $baseThatImplements->properties);
$this->assertNotEquals(UNDEFINED, $baseThatImplements->allOf);
$this->assertEquals(Generator::UNDEFINED, $baseThatImplements->properties);
$this->assertNotEquals(Generator::UNDEFINED, $baseThatImplements->allOf);
$this->assertCount(2, $baseThatImplements->allOf);
$traitUsedByExtendsBaseThatImplements = $schemas[3];
$this->assertSame('TraitUsedByExtendsBaseThatImplements', $traitUsedByExtendsBaseThatImplements->schema);
$this->assertEquals($traitUsedByExtendsBaseThatImplements->properties[0]->property, 'traitProperty');
$this->assertEquals(UNDEFINED, $traitUsedByExtendsBaseThatImplements->allOf);
$this->assertEquals(Generator::UNDEFINED, $traitUsedByExtendsBaseThatImplements->allOf);
}
}

View File

@ -9,9 +9,9 @@ namespace OpenApi\Tests\Processors;
use OpenApi\Analysis;
use OpenApi\Annotations\OpenApi;
use OpenApi\Annotations\Response;
use OpenApi\Generator;
use OpenApi\Processors\MergeIntoComponents;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
class MergeIntoComponentsTest extends OpenApiTestCase
{
@ -25,7 +25,7 @@ class MergeIntoComponentsTest extends OpenApiTestCase
$response,
]
);
$this->assertSame(UNDEFINED, $openapi->components);
$this->assertSame(Generator::UNDEFINED, $openapi->components);
$analysis->process(new MergeIntoComponents());
$this->assertCount(1, $openapi->components->responses);
$this->assertSame($response, $openapi->components->responses[0]);

View File

@ -9,9 +9,9 @@ namespace OpenApi\Tests\Processors;
use OpenApi\Analysis;
use OpenApi\Annotations\Info;
use OpenApi\Annotations\OpenApi;
use OpenApi\Generator;
use OpenApi\Processors\MergeIntoOpenApi;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
class MergeIntoOpenApiTest extends OpenApiTestCase
{
@ -26,7 +26,7 @@ class MergeIntoOpenApiTest extends OpenApiTestCase
]
);
$this->assertSame($openapi, $analysis->openapi);
$this->assertSame(UNDEFINED, $openapi->info);
$this->assertSame(Generator::UNDEFINED, $openapi->info);
$analysis->process(new MergeIntoOpenApi());
$this->assertSame($openapi, $analysis->openapi);
$this->assertSame($info, $openapi->info);

View File

@ -9,9 +9,9 @@ namespace OpenApi\Tests\Processors;
use OpenApi\Analysis;
use OpenApi\Annotations\Parameter;
use OpenApi\Annotations\Response;
use OpenApi\Generator;
use OpenApi\Processors\MergeJsonContent;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
class MergeJsonContentTest extends OpenApiTestCase
{
@ -27,7 +27,7 @@ END;
$analysis = new Analysis($this->parseComment($comment));
$this->assertCount(3, $analysis->annotations);
$response = $analysis->getAnnotationsOfType(Response::class)[0];
$this->assertSame(UNDEFINED, $response->content);
$this->assertSame(Generator::UNDEFINED, $response->content);
$this->assertCount(1, $response->_unmerged);
$analysis->process(new MergeJsonContent());
$this->assertCount(1, $response->content);
@ -64,7 +64,7 @@ END;
$analysis = new Analysis($this->parseComment($comment));
$this->assertCount(4, $analysis->annotations);
$parameter = $analysis->getAnnotationsOfType(Parameter::class)[0];
$this->assertSame(UNDEFINED, $parameter->content);
$this->assertSame(Generator::UNDEFINED, $parameter->content);
$this->assertCount(1, $parameter->_unmerged);
$analysis->process(new MergeJsonContent());
$this->assertCount(1, $parameter->content);

View File

@ -9,9 +9,9 @@ namespace OpenApi\Tests\Processors;
use OpenApi\Analysis;
use OpenApi\Annotations\Parameter;
use OpenApi\Annotations\Response;
use OpenApi\Generator;
use OpenApi\Processors\MergeXmlContent;
use OpenApi\Tests\OpenApiTestCase;
use const OpenApi\UNDEFINED;
class MergeXmlContentTest extends OpenApiTestCase
{
@ -27,7 +27,7 @@ END;
$analysis = new Analysis($this->parseComment($comment));
$this->assertCount(3, $analysis->annotations);
$response = $analysis->getAnnotationsOfType(Response::class)[0];
$this->assertSame(UNDEFINED, $response->content);
$this->assertSame(Generator::UNDEFINED, $response->content);
$this->assertCount(1, $response->_unmerged);
$analysis->process(new MergeXmlContent());
$this->assertCount(1, $response->content);
@ -64,7 +64,7 @@ END;
$analysis = new Analysis($this->parseComment($comment));
$this->assertCount(4, $analysis->annotations);
$parameter = $analysis->getAnnotationsOfType(Parameter::class)[0];
$this->assertSame(UNDEFINED, $parameter->content);
$this->assertSame(Generator::UNDEFINED, $parameter->content);
$this->assertCount(1, $parameter->_unmerged);
$analysis->process(new MergeXmlContent());
$this->assertCount(1, $parameter->content);

View File

@ -8,8 +8,8 @@ namespace OpenApi\Tests;
use OpenApi\Annotations;
use OpenApi\Annotations\OpenApi;
use OpenApi\Generator;
use OpenApi\Serializer;
use const OpenApi\UNDEFINED;
class SerializerTest extends OpenApiTestCase
{
@ -191,13 +191,13 @@ JSON;
foreach ($annotation->components->schemas as $schemaObject) {
$this->assertObjectHasAttribute('allOf', $schemaObject);
$this->assertNotSame($schemaObject->allOf, UNDEFINED);
$this->assertNotSame($schemaObject->allOf, Generator::UNDEFINED);
$this->assertIsArray($schemaObject->allOf);
$allOfItem = current($schemaObject->allOf);
$this->assertIsObject($allOfItem);
$this->assertInstanceOf(Annotations\Schema::class, $allOfItem);
$this->assertObjectHasAttribute('ref', $allOfItem);
$this->assertNotSame($allOfItem->ref, UNDEFINED);
$this->assertNotSame($allOfItem->ref, Generator::UNDEFINED);
$this->assertSame('#/components/schemas/SomeSchema', $allOfItem->ref);
}
}

View File

@ -10,6 +10,7 @@ use OpenApi\Analyser;
use OpenApi\Annotations\Property;
use OpenApi\Annotations\Schema;
use OpenApi\Context;
use OpenApi\Generator;
use OpenApi\StaticAnalyser;
use OpenApi\Tests\Fixtures\Parser\User;
@ -133,7 +134,7 @@ class StaticAnalyserTest extends OpenApiTestCase
// Allow the analyser to parse 3rd party annotations, which might
// contain useful info that could be extracted with a custom processor
Analyser::$whitelist[] = 'AnotherNamespace\\Annotations\\';
$openapi = \OpenApi\scan(__DIR__.'/Fixtures/ThirdPartyAnnotations.php');
$openapi = Generator::scan([__DIR__.'/Fixtures/ThirdPartyAnnotations.php']);
$this->assertSame('api/3rd-party', $openapi->paths[0]->path);
$this->assertCount(4, $openapi->_unmerged);
Analyser::$whitelist = $backup;

View File

@ -6,6 +6,7 @@
namespace OpenApi\Tests;
use OpenApi\Generator;
use OpenApi\Util;
use Symfony\Component\Finder\Finder;
@ -13,18 +14,17 @@ class UtilTest extends OpenApiTestCase
{
public function testExclude()
{
$openapi = \OpenApi\scan(__DIR__.'/Fixtures', [
'exclude' => [
'Customer.php',
'CustomerInterface.php',
'GrandAncestor.php',
'InheritProperties',
'Parser',
'Processors',
'UsingRefs.php',
'UsingPhpDoc.php',
],
]);
$exclude = [
'Customer.php',
'CustomerInterface.php',
'GrandAncestor.php',
'InheritProperties',
'Parser',
'Processors',
'UsingRefs.php',
'UsingPhpDoc.php',
];
$openapi = Generator::scan(Util::finder(__DIR__.'/Fixtures', $exclude));
$this->assertSame('Fixture for ParserTest', $openapi->info->title, 'No errors about duplicate @OA\Info() annotations');
}