welcome back to dyb-tech

This commit is contained in:
Daniel Guzman
2024-05-18 02:28:01 +02:00
parent 9513cdba09
commit 9f30bc98c7
6149 changed files with 668407 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
## Contributing
Contributions of any kind are welcome.
Feel free to submit [Github Issues](https://github.com/zircote/swagger-php/issues)
or [pull requests](https://github.com/zircote/swagger-php/pulls).
## Quick Guide
The documentation site has [some details](https://zircote.github.io/swagger-php/guide/under-the-hood.html#documentation) about internals.
### How-To
* [Fork](https://help.github.com/articles/fork-a-repo/) the repo.
* [Checkout](https://git-scm.com/docs/git-checkout) the branch you want to make changes on.
* Typically, this will be `master`. Note that most of the time, `master` represents the next release of swagger-php, so Pull Requests that break backwards compatibility might be postponed.
* Install dependencies: `composer install`.
* Create a new branch, e.g. `feature-foo` or `bugfix-bar`.
* Make changes.
* If you are adding functionality or fixing a bug - add a test!
Prefer adding new test cases over modifying existing ones.
* Update documentation: `composer docs:gen`.
* Run static analysis using PHPStan/Psalm: `composer analyse`.
* Check if tests pass: `composer test`.
* Fix code style issues: `composer cs`.
## Documentation
The documentation website is build from the [docs](docs/) folder with [vitepress](https://vitepress.vuejs.org).
This process involves converting the existing markdown (`.md`) files into static HTML pages and publishing them.
Some reference content is based on the existing code, so changes to annotations, attributes and processors will require to re-generate those markdown files: `composer docs:gen`.
The actual published content is managed in the [gh-pages](https://github.com/zircote/swagger-php/tree/gh-pages) branch and driven by a [publish action](https://github.com/zircote/swagger-php/actions/workflows/gh-pages.yml).
## Useful commands
### To run both unit tests and linting execute
```shell
composer test
```
### To run static-analysis execute
```shell
composer analyse
```
### Running unit tests only
```shell
./bin/phpunit
```
### Regenerate reference markup docs
```shell
composer docs:gen
```
### Running linting only
```shell
composer lint
```
### To make `php-cs-fixer` fix linting errors
```shell
composer cs
```
### Run dev server for local development of `gh-pages`
```shell
composer docs:dev
```
## Project's Standards
* [PSR-1: Basic Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
* [PSR-2: Coding Style Guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
* [PSR-4: Autoloading Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md)
* [PSR-5: PHPDoc (draft)](https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md)
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+2
View File
@@ -0,0 +1,2 @@
swagger-php
Copyright 2022 The swagger-php project
+126
View File
@@ -0,0 +1,126 @@
[![Build Status](https://img.shields.io/github/actions/workflow/status/zircote/swagger-php/build.yml?branch=master)](https://github.com/zircote/swagger-php/actions?query=workflow:build)
[![Total Downloads](https://img.shields.io/packagist/dt/zircote/swagger-php.svg)](https://packagist.org/packages/zircote/swagger-php)
[![License](https://img.shields.io/badge/license-Apache2.0-blue.svg)](LICENSE)
# swagger-php
Generate interactive [OpenAPI](https://www.openapis.org) documentation for your RESTful API using
[doctrine annotations](https://www.doctrine-project.org/projects/annotations.html) (optional as of version 4.8; if required the `doctrine/annotations` library must be installed in addition to swagger.php).
or [PHP attributes](https://www.php.net/manual/en/language.attributes.overview.php).
For a full list of supported annotations, please have look at the [`OpenApi\Annotations` namespace](src/Annotations) or the [documentation website](https://zircote.github.io/swagger-php/guide/annotations.html).
## Features
- Compatible with the OpenAPI **3.0** and **3.1** specification.
- Extracts information from code & existing phpdoc annotations.
- Command-line interface available.
- [Documentation site](https://zircote.github.io/swagger-php/) with a getting started guide.
- Exceptional error reporting (with hints, context)
- As of PHP 8.1 all annotations are also available as PHP attributes
## OpenAPI version support
`swagger-php` allows to generate specs either for **OpenAPI 3.0.0** or **OpenAPI 3.1.0**.
By default the spec will be in version `3.0.0`. The command line option `--version` may be used to change this
to `3.1.0`.
Programmatically, the method `Generator::setVersion()` can be used to change the version.
## Requirements
`swagger-php` requires at least PHP 7.2 for annotations and PHP 8.1 for using attributes.
## Installation (with [Composer](https://getcomposer.org))
```shell
composer require zircote/swagger-php
```
For cli usage from anywhere install swagger-php globally and make sure to place the `~/.composer/vendor/bin` directory in your PATH so the `openapi` executable can be located by your system.
```shell
composer global require zircote/swagger-php
```
### doctrine/annotations
As of version `4.8` the [doctrine annotations](https://www.doctrine-project.org/projects/annotations.html) library **is optional** and **no longer installed by default**.
To use PHPDoc annotations this needs to be installed on top of `swagger-php`:
```shell
composer require doctrine/annotations
```
If your code uses PHPDoc annotations you will need to install this as well:
```shell
composer require doctrine/annotations
```
## Usage
Add annotations to your php files.
```php
/**
* @OA\Info(title="My First API", version="0.1")
*/
/**
* @OA\Get(
* path="/api/resource.json",
* @OA\Response(response="200", description="An example resource")
* )
*/
```
Visit the [Documentation website](https://zircote.github.io/swagger-php/) for the [Getting started guide](https://zircote.github.io/swagger-php/guide) or look at the [Examples directory](Examples/) for more examples.
### Usage from php
Generate always-up-to-date documentation.
```php
<?php
require("vendor/autoload.php");
$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 reference](https://zircote.github.io/swagger-php/reference/generator).
### Usage from the Command Line Interface
The `openapi` command line interface can be used to generate the documentation to a static yaml/json file.
```shell
./vendor/bin/openapi --help
```
Starting with version 4 the default analyser used on the command line is the new `ReflectionAnalyser`.
Using the `--legacy` flag (`-l`) the legacy `TokenAnalyser` can still be used.
### Usage from the Deserializer
Generate the OpenApi annotation object from a json string, which makes it easier to manipulate objects programmatically.
```php
<?php
use OpenApi\Serializer;
$serializer = new Serializer();
$openapi = $serializer->deserialize($jsonString, 'OpenApi\Annotations\OpenApi');
echo $openapi->toJson();
```
## [Contributing](CONTRIBUTING.md)
## More on OpenApi & Swagger
- https://swagger.io
- https://www.openapis.org
- [OpenApi Documentation](https://swagger.io/docs/)
- [OpenApi Specification](http://swagger.io/specification/)
- [Related projects](docs/related-projects.md)
+248
View File
@@ -0,0 +1,248 @@
#!/usr/bin/env php
<?php
use OpenApi\Analysers\AttributeAnnotationFactory;
use OpenApi\Analysers\DocBlockAnnotationFactory;
use OpenApi\Analysers\ReflectionAnalyser;
use OpenApi\Analysers\TokenAnalyser;
use OpenApi\Annotations\OpenApi;
use OpenApi\Generator;
use OpenApi\Util;
use OpenApi\Loggers\ConsoleLogger;
if (class_exists(Generator::class) === false) {
if (file_exists(__DIR__.'/../vendor/autoload.php')) { // cloned / dev environment?
require_once(__DIR__.'/../vendor/autoload.php');
} else {
require_once(realpath(__DIR__.'/../../../').'/autoload.php');
}
}
error_reporting(E_ALL);
// Possible options and their default values.
$options = [
'config' => [],
'legacy' => false,
'output' => false,
'format' => 'auto',
'exclude' => [],
'pattern' => '*.php',
'bootstrap' => [],
'help' => false,
'debug' => false,
'processor' => [],
'version' => null,
];
$aliases = [
'c' => 'config',
'l' => 'legacy',
'o' => 'output',
'e' => 'exclude',
'n' => 'pattern',
'b' => 'bootstrap',
'h' => 'help',
'd' => 'debug',
'p' => 'processor',
'f' => 'format'
];
$needsArgument = [
'config',
'output',
'format',
'exclude',
'pattern',
'bootstrap',
'processor',
'version',
];
$paths = [];
$error = false;
try {
// Parse cli arguments
for ($i = 1; $i < $argc; $i++) {
$arg = $argv[$i];
if (substr($arg, 0, 2) === '--') { // longopt
$option = substr($arg, 2);
} elseif ($arg[0] === '-') { // shortopt
if (array_key_exists(substr($arg, 1), $aliases)) {
$option = $aliases[$arg[1]];
} else {
throw new Exception('Unknown option: "' . $arg . '"');
}
} else {
$paths[] = $arg;
continue;
}
if (array_key_exists($option, $options) === false) {
throw new Exception('Unknown option: "' . $arg . '"');
}
if (in_array($option, $needsArgument)) {
if (empty($argv[$i + 1]) || $argv[$i + 1][0] === '-') {
throw new Exception('Missing argument for "' . $arg . '"');
}
if (is_array($options[$option])) {
$options[$option][] = $argv[$i + 1];
} else {
$options[$option] = $argv[$i + 1];
}
$i++;
} else {
$options[$option] = true;
}
}
} catch (\Exception $e) {
$error = $e->getMessage();
}
$logger = new ConsoleLogger($options['debug']);
if (!$error && $options['bootstrap']) {
foreach ($options['bootstrap'] as $bootstrap) {
$filenames = glob($bootstrap);
if (false === $filenames) {
$error = 'Invalid `--bootstrap` value: "' . $bootstrap . '"';
break;
}
foreach ($filenames as $filename) {
if ($options['debug']) {
$logger->debug('Bootstrapping: ' . $filename);
}
require_once($filename);
}
}
}
if (count($paths) === 0) {
$error = 'Specify at least one path.';
}
if ($options['help'] === false && $error) {
$logger->error('', ['prefix' => '']);
$logger->error($error);
// Show help
$options['help'] = true;
}
$defaultVersion = OpenApi::DEFAULT_VERSION;
if ($options['help']) {
$help = <<<EOF
Usage: openapi [--option value] [/path/to/project ...]
Options:
--config (-c) Generator config
ex: -c operationId.hash=false
--legacy (-l) Use legacy TokenAnalyser; default is the new ReflectionAnalyser
--output (-o) Path to store the generated documentation.
ex: --output openapi.yaml
--exclude (-e) Exclude path(s).
ex: --exclude vendor,library/Zend
--pattern (-n) Pattern of files to scan.
ex: --pattern "*.php" or --pattern "/\.(phps|php)$/"
--bootstrap (-b) Bootstrap php file(s) for defining constants, etc.
ex: --bootstrap config/constants.php
--processor (-p) Register an additional processor.
--format (-f) Force yaml or json.
--debug (-d) Show additional error information.
--version The OpenAPI version; defaults to {$defaultVersion}.
--help (-h) Display this help message.
EOF;
$logger->info($help);
exit(1);
}
$errorTypes = [
E_ERROR => 'Error',
E_WARNING => 'Warning',
E_PARSE => 'Parser error',
E_NOTICE => 'Notice',
E_STRICT => 'Strict',
E_DEPRECATED => 'Deprecated',
E_CORE_ERROR => 'Error(Core)',
E_CORE_WARNING => 'Warning(Core)',
E_COMPILE_ERROR => 'Error(compile)',
E_COMPILE_WARNING => 'Warning(Compile)',
E_RECOVERABLE_ERROR => 'Error(Recoverable)',
E_USER_ERROR => 'Error',
E_USER_WARNING => 'Warning',
E_USER_NOTICE => 'Notice',
E_USER_DEPRECATED => 'Deprecated',
];
set_error_handler(function ($errno, $errstr, $file, $line) use ($errorTypes, $options, $logger) {
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
return;
}
$type = array_key_exists($errno, $errorTypes) ? $errorTypes[$errno] : 'Error';
if ($type === 'Deprecated') {
$logger->info($errstr, ['prefix' => $type . ': ']);
} else {
$logger->error($errstr, ['prefix' => $type . ': ']);
}
if ($options['debug']) {
$logger->info(' in '.$file.' on line '.$line);
}
if (substr($type, 0, 5) === 'Error') {
exit($errno);
}
});
set_exception_handler(function ($exception) use ($logger) {
$logger->error($exception);
exit($exception->getCode() ?: 1);
});
$exclude = null;
if ($options['exclude']) {
$exclude = $options['exclude'];
if (strpos($exclude[0], ',') !== false) {
$exploded = explode(',', $exclude[0]);
$logger->error('Comma-separated exclude paths are deprecated, use multiple --exclude statements: --exclude '.$exploded[0].' --exclude '.$exploded[1]);
$exclude[0] = array_shift($exploded);
$exclude = array_merge($exclude, $exploded);
}
}
$pattern = "*.php";
if ($options['pattern']) {
$pattern = $options['pattern'];
}
$generator = new Generator($logger);
foreach ($options["processor"] as $processor) {
$class = '\OpenApi\Processors\\'.$processor;
if (class_exists($class)) {
$processor = new $class();
} elseif (class_exists($processor)) {
$processor = new $processor();
}
$generator->addProcessor($processor);
}
$analyser = $options['legacy']
? new TokenAnalyser()
: new ReflectionAnalyser([new DocBlockAnnotationFactory(), new AttributeAnnotationFactory()]);
$openapi = $generator
->setVersion($options['version'])
->setConfig($options['config'])
->setAnalyser($analyser)
->generate(Util::finder($paths, $exclude, $pattern));
if ($options['output'] === false) {
if (strtolower($options['format']) === 'json') {
echo $openapi->toJson();
} else {
echo $openapi->toYaml();
}
echo "\n";
} else {
if (is_dir($options['output'])) {
$options['output'] .= '/openapi.yaml';
}
$openapi->saveAs($options['output'], $options['format']);
}
exit($logger->loggedMessageAboveNotice() ? 1 : 0);
+119
View File
@@ -0,0 +1,119 @@
{
"name": "zircote/swagger-php",
"type": "library",
"license": "Apache-2.0",
"bin": [
"bin/openapi"
],
"description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations",
"keywords": [
"json",
"rest",
"api",
"service discovery"
],
"homepage": "https://github.com/zircote/swagger-php/",
"authors": [
{
"name": "Robert Allen",
"email": "zircote@gmail.com"
},
{
"name": "Bob Fanger",
"email": "bfanger@gmail.com",
"homepage": "https://bfanger.nl"
},
{
"name": "Martin Rademacher",
"email": "mano@radebatz.net",
"homepage": "https://radebatz.net"
}
],
"config": {
"bin-dir": "bin",
"optimize-autoloader": true,
"sort-packages": true,
"allow-plugins": {
"composer/package-versions-deprecated": true
}
},
"minimum-stability": "stable",
"extra": {
"branch-alias": {
"dev-master": "4.x-dev"
}
},
"require": {
"php": ">=7.2",
"ext-json": "*",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"symfony/deprecation-contracts": "^2 || ^3",
"symfony/finder": ">=2.2",
"symfony/yaml": ">=3.3"
},
"autoload": {
"psr-4": {
"OpenApi\\": "src"
}
},
"require-dev": {
"composer/package-versions-deprecated": "^1.11",
"doctrine/annotations": "^1.7 || ^2.0",
"friendsofphp/php-cs-fixer": "^2.17 || ^3.47.1",
"phpstan/phpstan": "^1.6",
"phpunit/phpunit": ">=8",
"vimeo/psalm": "^4.23"
},
"suggest": {
"doctrine/annotations": "^1.7 || ^2.0"
},
"autoload-dev": {
"exclude-from-classmap": [
"/tests/Fixtures"
],
"psr-4": {
"OpenApi\\Tools\\": "tools/src/",
"OpenApi\\Tests\\": "tests/",
"AnotherNamespace\\": "tests/Fixtures/AnotherNamespace"
}
},
"scripts-descriptions": {
"cs": "Fix all codestyle issues",
"lint": "Test codestyle",
"test": "Run all non-legacy and codestyle tests",
"testlegacy": "Run tests using the legacy TokenAnalyser",
"testall": "Run all tests (test + testlegacy)",
"analyse": "Run static analysis (phpstan/psalm)",
"spectral": "Run spectral lint over all .yaml files in the Examples folder",
"docs:gen": "Rebuild reference documentation",
"docs:dev": "Run dev server for local development of gh-pages",
"docs:build": "Re-build static gh-pages"
},
"scripts": {
"cs": "export XDEBUG_MODE=off && php-cs-fixer fix --allow-risky=yes",
"lint": "@cs --dry-run",
"test": [
"export XDEBUG_MODE=off && phpunit",
"@lint"
],
"testlegacy": "export XDEBUG_MODE=off && export PHPUNIT_ANALYSER=legacy && phpunit",
"testall": [
"@test",
"@testlegacy"
],
"analyse": [
"export XDEBUG_MODE=off && phpstan analyse --memory-limit=2G",
"export XDEBUG_MODE=off && psalm"
],
"spectral": "for ff in `find Examples -name '*.yaml'`; do spectral lint $ff; done",
"docs:gen": [
"@php tools/refgen.php",
"@php tools/procgen.php"
],
"docs:dev": "cd docs && npm run dev",
"docs:build": [
"@docs:gen",
"cd docs && npm run build"
]
}
}
@@ -0,0 +1,18 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Analysers;
use OpenApi\Analysis;
use OpenApi\Context;
use OpenApi\Generator;
interface AnalyserInterface
{
public function setGenerator(Generator $generator): void;
public function fromFile(string $filename, Context $context): Analysis;
}
@@ -0,0 +1,26 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Analysers;
use OpenApi\Annotations as OA;
use OpenApi\Context;
use OpenApi\Generator;
interface AnnotationFactoryInterface
{
/**
* Checks if this factory is supported by the current runtime.
*/
public function isSupported(): bool;
public function setGenerator(Generator $generator): void;
/**
* @return array<OA\AbstractAnnotation> top level annotations
*/
public function build(\Reflector $reflector, Context $context): array;
}
@@ -0,0 +1,160 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Analysers;
use OpenApi\Annotations as OA;
use OpenApi\Context;
use OpenApi\Generator;
class AttributeAnnotationFactory implements AnnotationFactoryInterface
{
/** @var Generator|null */
protected $generator;
public function isSupported(): bool
{
return \PHP_VERSION_ID >= 80100;
}
public function setGenerator(Generator $generator): void
{
$this->generator = $generator;
}
public function build(\Reflector $reflector, Context $context): array
{
if (!$this->isSupported() || !method_exists($reflector, 'getAttributes')) {
return [];
}
if ($reflector instanceof \ReflectionProperty && method_exists($reflector, 'isPromoted') && $reflector->isPromoted()) {
// handled via __construct() parameter
return [];
}
// no proper way to inject
Generator::$context = $context;
/** @var OA\AbstractAnnotation[] $annotations */
$annotations = [];
try {
foreach ($reflector->getAttributes() as $attribute) {
if (class_exists($attribute->getName())) {
$instance = $attribute->newInstance();
if ($instance instanceof OA\AbstractAnnotation) {
$annotations[] = $instance;
}
} else {
$context->logger->debug(sprintf('Could not instantiate attribute "%s", because class not found.', $attribute->getName()));
}
}
if ($reflector instanceof \ReflectionMethod) {
// also look at parameter attributes
foreach ($reflector->getParameters() as $rp) {
foreach ([OA\Property::class, OA\Parameter::class, OA\RequestBody::class] as $attributeName) {
foreach ($rp->getAttributes($attributeName, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
/** @var OA\Property|OA\Parameter|OA\RequestBody $instance */
$instance = $attribute->newInstance();
$type = (($rnt = $rp->getType()) && $rnt instanceof \ReflectionNamedType) ? $rnt->getName() : Generator::UNDEFINED;
$nullable = $rnt ? $rnt->allowsNull() : true;
if ($instance instanceof OA\RequestBody) {
$instance->required = !$nullable;
} elseif ($instance instanceof OA\Property) {
if (Generator::isDefault($instance->property)) {
$instance->property = $rp->getName();
}
if (Generator::isDefault($instance->type)) {
$instance->type = $type;
}
$instance->nullable = $nullable ?: Generator::UNDEFINED;
if ($rp->isPromoted()) {
// promoted parameter - docblock is available via class/property
if ($comment = $rp->getDeclaringClass()->getProperty($rp->getName())->getDocComment()) {
$instance->_context->comment = $comment;
}
}
} else {
if (!$instance->name || Generator::isDefault($instance->name)) {
$instance->name = $rp->getName();
}
$instance->required = !$nullable;
$context = new Context(['nested' => $this], $context);
$context->comment = null;
$instance->merge([new OA\Schema(['type' => $type, '_context' => $context])]);
}
$annotations[] = $instance;
}
}
}
if (($rrt = $reflector->getReturnType()) && $rrt instanceof \ReflectionNamedType) {
foreach ($annotations as $annotation) {
if ($annotation instanceof OA\Property && Generator::isDefault($annotation->type)) {
// pick up simple return types
$annotation->type = $rrt->getName();
}
}
}
}
} finally {
Generator::$context = null;
}
$annotations = array_values(array_filter($annotations, function ($a) {
return $a instanceof OA\AbstractAnnotation;
}));
// merge backwards into parents...
$isParent = function (OA\AbstractAnnotation $annotation, OA\AbstractAnnotation $possibleParent): bool {
// regular annotation hierarchy
$explicitParent = null !== $possibleParent->matchNested($annotation) && !$annotation instanceof OA\Attachable;
$isParentAllowed = false;
// support Attachable subclasses
if ($isAttachable = $annotation instanceof OA\Attachable) {
if (!$isParentAllowed = (null === $annotation->allowedParents())) {
// check for allowed parents
foreach ($annotation->allowedParents() as $allowedParent) {
if ($possibleParent instanceof $allowedParent) {
$isParentAllowed = true;
break;
}
}
}
}
// Property can be nested...
return $annotation->getRoot() != $possibleParent->getRoot()
&& ($explicitParent || ($isAttachable && $isParentAllowed));
};
$annotationsWithoutParent = [];
foreach ($annotations as $index => $annotation) {
$mergedIntoParent = false;
for ($ii = 0; $ii < count($annotations); ++$ii) {
if ($ii === $index) {
continue;
}
$possibleParent = $annotations[$ii];
if ($isParent($annotation, $possibleParent)) {
$mergedIntoParent = true; //
$possibleParent->merge([$annotation]);
}
}
if (!$mergedIntoParent) {
$annotationsWithoutParent[] = $annotation;
}
}
return $annotationsWithoutParent;
}
}
@@ -0,0 +1,53 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Analysers;
use Composer\Autoload\ClassLoader;
/**
* Scans for classes/interfaces/traits.
*
* Relies on a `composer --optimized` run in order to utilize
* the generated class map.
*/
class ComposerAutoloaderScanner
{
/**
* Collect all classes/interfaces/traits known by composer.
*
* @param array<string> $namespaces
*
* @return array<string>
*/
public function scan(array $namespaces): array
{
$units = [];
if ($autoloader = $this->getComposerAutoloader()) {
foreach (array_keys($autoloader->getClassMap()) as $unit) {
foreach ($namespaces as $namespace) {
if (0 === strpos($unit, $namespace)) {
$units[] = $unit;
break;
}
}
}
}
return $units;
}
public static function getComposerAutoloader(): ?ClassLoader
{
foreach (spl_autoload_functions() as $fkt) {
if (is_array($fkt) && $fkt[0] instanceof ClassLoader) {
return $fkt[0];
}
}
return null;
}
}
@@ -0,0 +1,63 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Analysers;
use OpenApi\Context;
use OpenApi\Generator;
class DocBlockAnnotationFactory implements AnnotationFactoryInterface
{
/** @var DocBlockParser|null */
protected $docBlockParser = null;
/** @var Generator|null */
protected $generator = null;
public function __construct(?DocBlockParser $docBlockParser = null)
{
$this->docBlockParser = $docBlockParser ?: new DocBlockParser();
}
public function isSupported(): bool
{
return DocBlockParser::isEnabled();
}
public function setGenerator(Generator $generator): void
{
$this->generator = $generator;
$this->docBlockParser->setAliases($generator->getAliases());
}
public function build(\Reflector $reflector, Context $context): array
{
$aliases = $this->generator ? $this->generator->getAliases() : [];
if (method_exists($reflector, 'getShortName') && method_exists($reflector, 'getName')) {
$aliases[strtolower($reflector->getShortName())] = $reflector->getName();
}
if ($context->with('scanned')) {
$details = $context->scanned;
foreach ($details['uses'] as $alias => $name) {
$aliasKey = strtolower($alias);
if ($name != $alias && !array_key_exists($aliasKey, $aliases)) {
// real aliases only
$aliases[strtolower($alias)] = $name;
}
}
}
$this->docBlockParser->setAliases($aliases);
if (method_exists($reflector, 'getDocComment') && ($comment = $reflector->getDocComment())) {
return $this->docBlockParser->fromComment($comment, $context);
}
return [];
}
}
@@ -0,0 +1,93 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Analysers;
use Doctrine\Common\Annotations\DocParser;
use OpenApi\Annotations as OA;
use OpenApi\Context;
use OpenApi\Generator;
/**
* Extract swagger-php annotations from a [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) using Doctrine's DocParser.
*/
class DocBlockParser
{
/**
* @var DocParser
*/
protected $docParser;
/**
* @param array<string, class-string> $aliases
*/
public function __construct(array $aliases = [])
{
if (DocBlockParser::isEnabled()) {
$docParser = new DocParser();
$docParser->setIgnoreNotImportedAnnotations(true);
$docParser->setImports($aliases);
$this->docParser = $docParser;
}
}
/**
* Check if we can process annotations.
*/
public static function isEnabled(): bool
{
return class_exists('Doctrine\\Common\\Annotations\\DocParser');
}
/**
* @param array<string, class-string> $aliases
*/
public function setAliases(array $aliases): void
{
$this->docParser->setImports($aliases);
}
/**
* Use doctrine to parse the comment block and return the detected annotations.
*
* @param string $comment a T_DOC_COMMENT
* @param Context $context
*
* @return array<OA\AbstractAnnotation>
*/
public function fromComment(string $comment, Context $context): array
{
$context->comment = $comment;
try {
Generator::$context = $context;
if ($context->is('annotations') === false) {
$context->annotations = [];
}
return $this->docParser->parse($comment, $context->getDebugLocation());
} catch (\Exception $e) {
if (preg_match('/^(.+) at position ([0-9]+) in ' . preg_quote((string) $context, '/') . '\.$/', $e->getMessage(), $matches)) {
$errorMessage = $matches[1];
$errorPos = (int) $matches[2];
$atPos = strpos($comment, '@');
$context->line -= substr_count($comment, "\n", $atPos + $errorPos) + 1;
$lines = explode("\n", substr($comment, $atPos, $errorPos));
$context->character = strlen(array_pop($lines)) + 1; // position starts at 0 character starts at 1
$context->logger->error($errorMessage . ' in ' . $context, ['exception' => $e]);
} else {
$context->logger->error(
$e->getMessage() . ($context->filename ? ('; file=' . $context->filename) : ''),
['exception' => $e]
);
}
return [];
} finally {
Generator::$context = null;
}
}
}
@@ -0,0 +1,195 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Analysers;
use OpenApi\Analysis;
use OpenApi\Annotations as OA;
use OpenApi\Context;
use OpenApi\Generator;
/**
* OpenApi analyser using reflection.
*
* Can read either PHP `DocBlock`s or `Attribute`s.
*
* Due to the nature of reflection this requires all related classes
* to be auto-loadable.
*/
class ReflectionAnalyser implements AnalyserInterface
{
/** @var AnnotationFactoryInterface[] */
protected $annotationFactories;
/** @var Generator|null */
protected $generator;
/**
* @param array<AnnotationFactoryInterface> $annotationFactories
*/
public function __construct(array $annotationFactories = [])
{
$this->annotationFactories = [];
foreach ($annotationFactories as $annotationFactory) {
if ($annotationFactory->isSupported()) {
$this->annotationFactories[] = $annotationFactory;
}
}
if (!$this->annotationFactories) {
throw new \RuntimeException('No suitable annotation factory found. At least one of "Doctrine Annotations" or PHP 8.1 are required');
}
}
public function setGenerator(Generator $generator): void
{
$this->generator = $generator;
foreach ($this->annotationFactories as $annotationFactory) {
$annotationFactory->setGenerator($generator);
}
}
public function fromFile(string $filename, Context $context): Analysis
{
$scanner = new TokenScanner();
$fileDetails = $scanner->scanFile($filename);
$analysis = new Analysis([], $context);
foreach ($fileDetails as $fqdn => $details) {
$this->analyzeFqdn($fqdn, $analysis, $details);
}
return $analysis;
}
public function fromFqdn(string $fqdn, Analysis $analysis): Analysis
{
$fqdn = ltrim($fqdn, '\\');
$rc = new \ReflectionClass($fqdn);
if (!$filename = $rc->getFileName()) {
return $analysis;
}
$scanner = new TokenScanner();
$fileDetails = $scanner->scanFile($filename);
$this->analyzeFqdn($fqdn, $analysis, $fileDetails[$fqdn]);
return $analysis;
}
protected function analyzeFqdn(string $fqdn, Analysis $analysis, array $details): Analysis
{
if (!class_exists($fqdn) && !interface_exists($fqdn) && !trait_exists($fqdn) && (!function_exists('enum_exists') || !enum_exists($fqdn))) {
$analysis->context->logger->warning('Skipping unknown ' . $fqdn);
return $analysis;
}
$rc = new \ReflectionClass($fqdn);
$contextType = $rc->isInterface() ? 'interface' : ($rc->isTrait() ? 'trait' : ((method_exists($rc, 'isEnum') && $rc->isEnum()) ? 'enum' : 'class'));
$context = new Context([
$contextType => $rc->getShortName(),
'namespace' => $rc->getNamespaceName() ?: null,
'uses' => $details['uses'],
'comment' => $rc->getDocComment() ?: null,
'filename' => $rc->getFileName() ?: null,
'line' => $rc->getStartLine(),
'annotations' => [],
'scanned' => $details,
], $analysis->context);
$definition = [
$contextType => $rc->getShortName(),
'extends' => null,
'implements' => [],
'traits' => [],
'properties' => [],
'methods' => [],
'context' => $context,
];
$normaliseClass = function (string $name): string {
return '\\' . ltrim($name, '\\');
};
if ($parentClass = $rc->getParentClass()) {
$definition['extends'] = $normaliseClass($parentClass->getName());
}
$definition[$contextType == 'class' ? 'implements' : 'extends'] = array_map($normaliseClass, $details['interfaces']);
$definition['traits'] = array_map($normaliseClass, $details['traits']);
foreach ($this->annotationFactories as $annotationFactory) {
$analysis->addAnnotations($annotationFactory->build($rc, $context), $context);
}
foreach ($rc->getMethods() as $method) {
if (in_array($method->name, $details['methods'])) {
$definition['methods'][$method->getName()] = $ctx = new Context([
'method' => $method->getName(),
'comment' => $method->getDocComment() ?: null,
'filename' => $method->getFileName() ?: null,
'line' => $method->getStartLine(),
'annotations' => [],
], $context);
foreach ($this->annotationFactories as $annotationFactory) {
$analysis->addAnnotations($annotationFactory->build($method, $ctx), $ctx);
}
}
}
foreach ($rc->getProperties() as $property) {
if (in_array($property->name, $details['properties'])) {
$definition['properties'][$property->getName()] = $ctx = new Context([
'property' => $property->getName(),
'comment' => $property->getDocComment() ?: null,
'annotations' => [],
], $context);
if ($property->isStatic()) {
$ctx->static = true;
}
if (\PHP_VERSION_ID >= 70400 && ($type = $property->getType())) {
$ctx->nullable = $type->allowsNull();
if ($type instanceof \ReflectionNamedType) {
$ctx->type = $type->getName();
// Context::fullyQualifiedName(...) expects this
if (class_exists($absFqn = '\\' . $ctx->type)) {
$ctx->type = $absFqn;
}
}
}
foreach ($this->annotationFactories as $annotationFactory) {
$analysis->addAnnotations($annotationFactory->build($property, $ctx), $ctx);
}
}
}
foreach ($rc->getReflectionConstants() as $constant) {
foreach ($this->annotationFactories as $annotationFactory) {
$definition['constants'][$constant->getName()] = $ctx = new Context([
'constant' => $constant->getName(),
'comment' => $constant->getDocComment() ?: null,
'annotations' => [],
], $context);
foreach ($annotationFactory->build($constant, $ctx) as $annotation) {
if ($annotation instanceof OA\Property) {
if (Generator::isDefault($annotation->property)) {
$annotation->property = $constant->getName();
}
if (Generator::isDefault($annotation->const)) {
$annotation->const = $constant->getValue();
}
$analysis->addAnnotation($annotation, $ctx);
}
}
}
}
$addDefinition = 'add' . ucfirst($contextType) . 'Definition';
$analysis->{$addDefinition}($definition);
return $analysis;
}
}
@@ -0,0 +1,641 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Analysers;
use OpenApi\Analysis;
use OpenApi\Context;
use OpenApi\Generator;
/**
* Extracts swagger-php annotations from php code using static analysis.
*/
class TokenAnalyser implements AnalyserInterface
{
/** @var Generator|null */
protected $generator;
public function setGenerator(Generator $generator): void
{
$this->generator = $generator;
}
/**
* Extract and process all doc-comments from a file.
*
* @param string $filename path to a php file
*/
public function fromFile(string $filename, Context $context): Analysis
{
if (function_exists('opcache_get_status') && function_exists('opcache_get_configuration')) {
if (empty($GLOBALS['openapi_opcache_warning'])) {
$GLOBALS['openapi_opcache_warning'] = true;
$status = opcache_get_status();
$config = opcache_get_configuration();
if (is_array($status) && $status['opcache_enabled'] && $config['directives']['opcache.save_comments'] == false) {
$context->logger->error("php.ini \"opcache.save_comments = 0\" interferes with extracting annotations.\n[LINK] https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments");
}
}
}
$tokens = token_get_all(file_get_contents($filename));
return $this->fromTokens($tokens, new Context(['filename' => $filename], $context));
}
/**
* Extract and process all doc-comments from the contents.
*
* @param string $code PHP code. (including <?php tags)
* @param Context $context the original location of the contents
*/
public function fromCode(string $code, Context $context): Analysis
{
$tokens = token_get_all($code);
return $this->fromTokens($tokens, $context);
}
/**
* Shared implementation for parseFile() & parseContents().
*
* @param array $tokens The result of a token_get_all()
*/
protected function fromTokens(array $tokens, Context $parseContext): Analysis
{
$generator = $this->generator ?: new Generator();
$analysis = new Analysis([], $parseContext);
$docBlockParser = new DocBlockParser($generator->getAliases());
reset($tokens);
$token = '';
$aliases = $generator->getAliases();
$parseContext->uses = [];
// default to parse context to start with
$schemaContext = $parseContext;
$classDefinition = false;
$interfaceDefinition = false;
$traitDefinition = false;
$enumDefinition = false;
$comment = false;
$line = 0;
$lineOffset = $parseContext->line ?: 0;
while ($token !== false) {
$previousToken = $token;
$token = $this->nextToken($tokens, $parseContext);
if (is_array($token) === false) {
// Ignore tokens like "{", "}", etc
continue;
}
if (defined('T_ATTRIBUTE') && $token[0] === T_ATTRIBUTE) {
// consume
$this->parseAttribute($tokens, $token, $parseContext);
continue;
}
if ($token[0] === T_DOC_COMMENT) {
if ($comment) {
// 2 Doc-comments in succession?
$this->analyseComment($analysis, $docBlockParser, $comment, new Context(['line' => $line], $schemaContext));
}
$comment = $token[1];
$line = $token[2] + $lineOffset;
continue;
}
if (in_array($token[0], [T_ABSTRACT, T_FINAL])) {
// skip
$token = $this->nextToken($tokens, $parseContext);
}
if ($token[0] === T_CLASS) {
// Doc-comment before a class?
if (is_array($previousToken) && $previousToken[0] === T_DOUBLE_COLON) {
// php 5.5 class name resolution (i.e. ClassName::class)
continue;
}
$token = $this->nextToken($tokens, $parseContext);
if (is_string($token) && ($token === '(' || $token === '{')) {
// php7 anonymous classes (i.e. new class() { public function foo() {} };)
continue;
}
if (is_array($token) && ($token[1] === 'extends' || $token[1] === 'implements')) {
// php7 anonymous classes with extends (i.e. new class() extends { public function foo() {} };)
continue;
}
if (!is_array($token)) {
// PHP 8 named argument
continue;
}
$interfaceDefinition = false;
$traitDefinition = false;
$enumDefinition = false;
$schemaContext = new Context(['class' => $token[1], 'line' => $token[2]], $parseContext);
if ($classDefinition) {
$analysis->addClassDefinition($classDefinition);
}
$classDefinition = [
'class' => $token[1],
'extends' => null,
'properties' => [],
'methods' => [],
'context' => $schemaContext,
];
$token = $this->nextToken($tokens, $parseContext);
if ($token[0] === T_EXTENDS) {
$schemaContext->extends = $this->parseNamespace($tokens, $token, $parseContext);
$classDefinition['extends'] = $schemaContext->fullyQualifiedName($schemaContext->extends);
}
if ($token[0] === T_IMPLEMENTS) {
$schemaContext->implements = $this->parseNamespaceList($tokens, $token, $parseContext);
$classDefinition['implements'] = array_map([$schemaContext, 'fullyQualifiedName'], $schemaContext->implements);
}
if ($comment) {
$schemaContext->line = $line;
$this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext);
$comment = false;
continue;
}
// @todo detect end-of-class and reset $schemaContext
}
if ($token[0] === T_INTERFACE) { // Doc-comment before an interface?
$classDefinition = false;
$traitDefinition = false;
$enumDefinition = false;
$token = $this->nextToken($tokens, $parseContext);
if (!is_array($token)) {
// PHP 8 named argument
continue;
}
$schemaContext = new Context(['interface' => $token[1], 'line' => $token[2]], $parseContext);
if ($interfaceDefinition) {
$analysis->addInterfaceDefinition($interfaceDefinition);
}
$interfaceDefinition = [
'interface' => $token[1],
'extends' => null,
'properties' => [],
'methods' => [],
'context' => $schemaContext,
];
$token = $this->nextToken($tokens, $parseContext);
if ($token[0] === T_EXTENDS) {
$schemaContext->extends = $this->parseNamespaceList($tokens, $token, $parseContext);
$interfaceDefinition['extends'] = array_map([$schemaContext, 'fullyQualifiedName'], $schemaContext->extends);
}
if ($comment) {
$schemaContext->line = $line;
$this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext);
$comment = false;
continue;
}
// @todo detect end-of-interface and reset $schemaContext
}
if ($token[0] === T_TRAIT) {
$classDefinition = false;
$interfaceDefinition = false;
$enumDefinition = false;
$token = $this->nextToken($tokens, $parseContext);
if (!is_array($token)) {
// PHP 8 named argument
continue;
}
$schemaContext = new Context(['trait' => $token[1], 'line' => $token[2]], $parseContext);
if ($traitDefinition) {
$analysis->addTraitDefinition($traitDefinition);
}
$traitDefinition = [
'trait' => $token[1],
'properties' => [],
'methods' => [],
'context' => $schemaContext,
];
if ($comment) {
$schemaContext->line = $line;
$this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext);
$comment = false;
continue;
}
// @todo detect end-of-trait and reset $schemaContext
}
if (defined('T_ENUM') && $token[0] === T_ENUM) {
$classDefinition = false;
$interfaceDefinition = false;
$traitDefinition = false;
$token = $this->nextToken($tokens, $parseContext);
if (!is_array($token)) {
// PHP 8 named argument
continue;
}
$schemaContext = new Context(['enum' => $token[1], 'line' => $token[2]], $parseContext);
if ($enumDefinition) {
$analysis->addEnumDefinition($enumDefinition);
}
$enumDefinition = [
'enum' => $token[1],
'properties' => [],
'methods' => [],
'context' => $schemaContext,
];
if ($comment) {
$schemaContext->line = $line;
$this->analyseComment($analysis, $docBlockParser, $comment, $schemaContext);
$comment = false;
continue;
}
// @todo detect end-of-trait and reset $schemaContext
}
if ($token[0] === T_STATIC) {
$token = $this->nextToken($tokens, $parseContext);
if ($token[0] === T_VARIABLE) {
// static property
$propertyContext = new Context(
[
'property' => substr($token[1], 1),
'static' => true,
'line' => $line,
],
$schemaContext
);
if ($classDefinition) {
$classDefinition['properties'][$propertyContext->property] = $propertyContext;
}
if ($traitDefinition) {
$traitDefinition['properties'][$propertyContext->property] = $propertyContext;
}
if ($comment) {
$this->analyseComment($analysis, $docBlockParser, $comment, $propertyContext);
$comment = false;
}
continue;
}
}
if (in_array($token[0], [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR])) { // Scope
[$type, $nullable, $token] = $this->parseTypeAndNextToken($tokens, $parseContext);
if ($token[0] === T_VARIABLE) {
// instance property
$propertyContext = new Context(
[
'property' => substr($token[1], 1),
'type' => $type,
'nullable' => $nullable,
'line' => $line,
],
$schemaContext
);
if ($classDefinition) {
$classDefinition['properties'][$propertyContext->property] = $propertyContext;
}
if ($interfaceDefinition) {
$interfaceDefinition['properties'][$propertyContext->property] = $propertyContext;
}
if ($traitDefinition) {
$traitDefinition['properties'][$propertyContext->property] = $propertyContext;
}
if ($comment) {
$this->analyseComment($analysis, $docBlockParser, $comment, $propertyContext);
$comment = false;
}
} elseif ($token[0] === T_FUNCTION) {
$token = $this->nextToken($tokens, $parseContext);
if ($token[0] === T_STRING) {
$methodContext = new Context(
[
'method' => $token[1],
'line' => $line,
],
$schemaContext
);
if ($classDefinition) {
$classDefinition['methods'][$token[1]] = $methodContext;
}
if ($interfaceDefinition) {
$interfaceDefinition['methods'][$token[1]] = $methodContext;
}
if ($traitDefinition) {
$traitDefinition['methods'][$token[1]] = $methodContext;
}
if ($comment) {
$this->analyseComment($analysis, $docBlockParser, $comment, $methodContext);
$comment = false;
}
}
}
continue;
} elseif ($token[0] === T_FUNCTION) {
$token = $this->nextToken($tokens, $parseContext);
if ($token[0] === T_STRING) {
$methodContext = new Context(
[
'method' => $token[1],
'line' => $line,
],
$schemaContext
);
if ($classDefinition) {
$classDefinition['methods'][$token[1]] = $methodContext;
}
if ($interfaceDefinition) {
$interfaceDefinition['methods'][$token[1]] = $methodContext;
}
if ($traitDefinition) {
$traitDefinition['methods'][$token[1]] = $methodContext;
}
if ($comment) {
$this->analyseComment($analysis, $docBlockParser, $comment, $methodContext);
$comment = false;
}
}
}
if (in_array($token[0], [T_NAMESPACE, T_USE]) === false) {
// Skip "use" & "namespace" to prevent "never imported" warnings)
if ($comment) {
// Not a doc-comment for a class, property or method?
$this->analyseComment($analysis, $docBlockParser, $comment, new Context(['line' => $line], $schemaContext));
$comment = false;
}
}
if ($token[0] === T_NAMESPACE) {
$parseContext->namespace = $this->parseNamespace($tokens, $token, $parseContext);
$aliases['__NAMESPACE__'] = $parseContext->namespace;
$docBlockParser->setAliases($aliases);
continue;
}
if ($token[0] === T_USE) {
$statements = $this->parseUseStatement($tokens, $token, $parseContext);
foreach ($statements as $alias => $target) {
if ($classDefinition) {
// class traits
$classDefinition['traits'][] = $schemaContext->fullyQualifiedName($target);
} elseif ($traitDefinition) {
// trait traits
$traitDefinition['traits'][] = $schemaContext->fullyQualifiedName($target);
} else {
// not a trait use
$parseContext->uses[$alias] = $target;
$namespaces = $generator->getNamespaces();
if (null === $namespaces) {
$aliases[strtolower($alias)] = $target;
} else {
foreach ($namespaces as $namespace) {
if (strcasecmp(substr($target . '\\', 0, strlen($namespace)), $namespace) === 0) {
$aliases[strtolower($alias)] = $target;
break;
}
}
}
$docBlockParser->setAliases($aliases);
}
}
}
}
// cleanup final comment and definition
if ($comment) {
$this->analyseComment($analysis, $docBlockParser, $comment, new Context(['line' => $line], $schemaContext));
}
if ($classDefinition) {
$analysis->addClassDefinition($classDefinition);
}
if ($interfaceDefinition) {
$analysis->addInterfaceDefinition($interfaceDefinition);
}
if ($traitDefinition) {
$analysis->addTraitDefinition($traitDefinition);
}
if ($enumDefinition) {
$analysis->addEnumDefinition($enumDefinition);
}
return $analysis;
}
/**
* Parse comment and add annotations to analysis.
*/
private function analyseComment(Analysis $analysis, DocBlockParser $docBlockParser, string $comment, Context $context): void
{
$analysis->addAnnotations($docBlockParser->fromComment($comment, $context), $context);
}
/**
* The next non-whitespace, non-comment token.
*
*
* @return array|string The next token (or false)
*/
private function nextToken(array &$tokens, Context $context)
{
while (true) {
$token = next($tokens);
if (is_array($token)) {
if ($token[0] === T_WHITESPACE) {
continue;
}
if ($token[0] === T_COMMENT) {
$pos = strpos($token[1], '@OA\\');
if ($pos) {
$line = $context->line ? $context->line + $token[2] : $token[2];
$commentContext = new Context(['line' => $line], $context);
$context->logger->warning('Annotations are only parsed inside `/**` DocBlocks, skipping ' . $commentContext);
}
continue;
}
}
return $token;
}
}
private function parseAttribute(array &$tokens, &$token, Context $parseContext): void
{
$nesting = 1;
while ($token !== false) {
$token = $this->nextToken($tokens, $parseContext);
if (!is_array($token) && '[' === $token) {
++$nesting;
continue;
}
if (!is_array($token) && ']' === $token) {
--$nesting;
if (!$nesting) {
break;
}
}
}
}
/**
* @return int[]
*/
private function php8NamespaceToken(): array
{
return defined('T_NAME_QUALIFIED') ? [T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED] : [];
}
/**
* Parse namespaced string.
*
* @param array|string $token
*/
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], $nsToken)) {
break;
}
$namespace .= $token[1];
}
return $namespace;
}
/**
* Parse comma separated list of namespaced strings.
*
* @param array|string $token
*/
private function parseNamespaceList(array &$tokens, &$token, Context $parseContext): array
{
$namespaces = [];
while ($namespace = $this->parseNamespace($tokens, $token, $parseContext)) {
$namespaces[] = $namespace;
if ($token != ',') {
break;
}
}
return $namespaces;
}
/**
* Parse a use statement.
*
* @param (int|mixed)[]|string $token
*/
private function parseUseStatement(array &$tokens, &$token, Context $parseContext): array
{
$normalizeAlias = function ($alias): string {
$alias = ltrim($alias, '\\');
$elements = explode('\\', $alias);
return array_pop($elements);
};
$class = '';
$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], $nsToken);
if (!$explicitAlias && $isNameToken) {
$class .= $token[1];
$alias = $token[1];
} elseif ($explicitAlias && $isNameToken) {
$alias .= $token[1];
} elseif ($token[0] === T_AS) {
$explicitAlias = true;
$alias = '';
} elseif ($token === ',') {
$statements[$normalizeAlias($alias)] = $class;
$class = '';
$alias = '';
$explicitAlias = false;
} elseif ($token === ';') {
$statements[$normalizeAlias($alias)] = $class;
break;
} else {
break;
}
}
return $statements;
}
/**
* Parse type of variable (if it exists).
*/
private function parseTypeAndNextToken(array &$tokens, Context $parseContext): array
{
$type = Generator::UNDEFINED;
$nullable = false;
$token = $this->nextToken($tokens, $parseContext);
if ($token[0] === T_STATIC) {
$token = $this->nextToken($tokens, $parseContext);
}
if ($token === '?') { // nullable type
$nullable = true;
$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], $qualifiedToken)) {
if (in_array($token[0], $typeToken)) {
$type = $token[1];
}
$token = $this->nextToken($tokens, $parseContext);
}
return [$type, $nullable, $token];
}
}
@@ -0,0 +1,381 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Analysers;
/**
* High level, PHP token based, scanner.
*/
class TokenScanner
{
/**
* Scan file for all classes, interfaces and traits.
*
* @return string[][] File details
*/
public function scanFile(string $filename): array
{
return $this->scanTokens(token_get_all(file_get_contents($filename)));
}
/**
* Scan file for all classes, interfaces and traits.
*
* @return array<string, array<string, mixed>> File details
*/
protected function scanTokens(array $tokens): array
{
$units = [];
$uses = [];
$isInterface = false;
$isAbstractFunction = false;
$namespace = '';
$currentName = null;
$unitLevel = 0;
$lastToken = null;
$stack = [];
$initUnit = function ($uses): array {
return [
'uses' => $uses,
'interfaces' => [],
'traits' => [],
'enums' => [],
'methods' => [],
'properties' => [],
];
};
while (false !== ($token = $this->nextToken($tokens))) {
// named arguments
$nextToken = $this->nextToken($tokens);
if (($token !== '}' && $nextToken === ':') || $nextToken === false) {
continue;
}
do {
$prevToken = prev($tokens);
} while ($token !== $prevToken);
if (!is_array($token)) {
switch ($token) {
case '{':
$stack[] = $token;
break;
case '}':
array_pop($stack);
if (count($stack) == $unitLevel) {
$currentName = null;
}
break;
}
continue;
}
switch ($token[0]) {
case T_ABSTRACT:
if (count($stack)) {
$isAbstractFunction = true;
}
break;
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
$stack[] = $token[1];
break;
case T_NAMESPACE:
$namespace = $this->nextWord($tokens);
break;
case T_USE:
if (!$stack) {
$uses = array_merge($uses, $this->parseFQNStatement($tokens, $token));
} elseif ($currentName) {
$traits = $this->resolveFQN($this->parseFQNStatement($tokens, $token), $namespace, $uses);
$units[$currentName]['traits'] = array_merge($units[$currentName]['traits'], $traits);
}
break;
case T_CLASS:
if ($currentName) {
break;
}
if ($lastToken && is_array($lastToken) && $lastToken[0] === T_DOUBLE_COLON) {
// ::class
break;
}
// class name
$token = $this->nextToken($tokens);
// unless ...
if (is_string($token) && ($token === '(' || $token === '{')) {
// new class[()] { ... }
if ('{' == $token) {
prev($tokens);
}
break;
} elseif (is_array($token) && in_array($token[1], ['extends', 'implements'])) {
// new class[()] extends { ... }
break;
}
$isInterface = false;
$currentName = $namespace . '\\' . $token[1];
$unitLevel = count($stack);
$units[$currentName] = $initUnit($uses);
break;
case T_INTERFACE:
if ($currentName) {
break;
}
$isInterface = true;
$token = $this->nextToken($tokens);
$currentName = $namespace . '\\' . $token[1];
$unitLevel = count($stack);
$units[$currentName] = $initUnit($uses);
break;
case T_EXTENDS:
$fqns = $this->parseFQNStatement($tokens, $token);
if ($isInterface && $currentName) {
$units[$currentName]['interfaces'] = $this->resolveFQN($fqns, $namespace, $uses);
}
if (!is_array($token) || T_IMPLEMENTS !== $token[0]) {
break;
}
// no break
case T_IMPLEMENTS:
$fqns = $this->parseFQNStatement($tokens, $token);
if ($currentName) {
$units[$currentName]['interfaces'] = $this->resolveFQN($fqns, $namespace, $uses);
}
break;
case T_FUNCTION:
$token = $this->nextToken($tokens);
if ((!is_array($token) && '&' == $token)
|| (defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') && T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG == $token[0])) {
$token = $this->nextToken($tokens);
}
if (($unitLevel + 1) == count($stack) && $currentName) {
$units[$currentName]['methods'][] = $token[1];
if (!$isInterface && !$isAbstractFunction) {
// more nesting
$units[$currentName]['properties'] = array_merge(
$units[$currentName]['properties'],
$this->parsePromotedProperties($tokens)
);
$this->skipTo($tokens, '{', true);
} else {
// no function body
$this->skipTo($tokens, ';');
$isAbstractFunction = false;
}
}
break;
case T_VARIABLE:
if (($unitLevel + 1) == count($stack) && $currentName) {
$units[$currentName]['properties'][] = substr($token[1], 1);
}
break;
default:
// handle trait here too to avoid duplication
if (T_TRAIT === $token[0] || (defined('T_ENUM') && T_ENUM === $token[0])) {
if ($currentName) {
break;
}
$isInterface = false;
$token = $this->nextToken($tokens);
$currentName = $namespace . '\\' . $token[1];
$unitLevel = count($stack);
$this->skipTo($tokens, '{', true);
$units[$currentName] = $initUnit($uses);
}
break;
}
$lastToken = $token;
}
return $units;
}
/**
* Get the next token that is not whitespace or comment.
*
* @return string|array|false
*/
protected function nextToken(array &$tokens)
{
$token = true;
while ($token) {
$token = next($tokens);
if (is_array($token)) {
if (in_array($token[0], [T_WHITESPACE, T_COMMENT])) {
continue;
}
}
return $token;
}
return $token;
}
/**
* @return array<string>
*/
protected function resolveFQN(array $names, string $namespace, array $uses): array
{
$resolve = function ($name) use ($namespace, $uses) {
if ('\\' == $name[0]) {
return substr($name, 1);
}
if (array_key_exists($name, $uses)) {
return $uses[$name];
}
return $namespace . '\\' . $name;
};
return array_values(array_map($resolve, $names));
}
protected function skipTo(array &$tokens, string $char, bool $prev = false): void
{
while (false !== ($token = next($tokens))) {
if (is_string($token) && $token == $char) {
if ($prev) {
prev($tokens);
}
break;
}
}
}
/**
* Read next word.
*
* Skips leading whitespace.
*/
protected function nextWord(array &$tokens): string
{
$word = '';
while (false !== ($token = next($tokens))) {
if (is_array($token)) {
if ($token[0] === T_WHITESPACE) {
if ($word) {
break;
}
continue;
}
$word .= $token[1];
}
}
return $word;
}
/**
* Parse a use statement.
*/
protected function parseFQNStatement(array &$tokens, array &$token): array
{
$normalizeAlias = function ($alias): string {
$alias = ltrim($alias, '\\');
$elements = explode('\\', $alias);
return array_pop($elements);
};
$class = '';
$alias = '';
$statements = [];
$explicitAlias = false;
$php8NSToken = defined('T_NAME_QUALIFIED') ? [T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED] : [];
$nsToken = array_merge([T_STRING, T_NS_SEPARATOR], $php8NSToken);
while ($token !== false) {
$token = $this->nextToken($tokens);
$isNameToken = in_array($token[0], $nsToken);
if (!$explicitAlias && $isNameToken) {
$class .= $token[1];
$alias = $token[1];
} elseif ($explicitAlias && $isNameToken) {
$alias .= $token[1];
} elseif ($token[0] === T_AS) {
$explicitAlias = true;
$alias = '';
} elseif ($token[0] === T_IMPLEMENTS) {
$statements[$normalizeAlias($alias)] = $class;
break;
} elseif ($token === ',') {
$statements[$normalizeAlias($alias)] = $class;
$class = '';
$alias = '';
$explicitAlias = false;
} elseif ($token === ';') {
$statements[$normalizeAlias($alias)] = $class;
break;
} elseif ($token === '{') {
$statements[$normalizeAlias($alias)] = $class;
prev($tokens);
break;
} else {
break;
}
}
return $statements;
}
protected function parsePromotedProperties(array &$tokens): array
{
$properties = [];
$this->skipTo($tokens, '(');
$round = 1;
$promoted = false;
while (false !== ($token = $this->nextToken($tokens))) {
if (is_string($token)) {
switch ($token) {
case '(':
++$round;
break;
case ')':
--$round;
if (0 == $round) {
return $properties;
}
}
}
if (is_array($token)) {
switch ($token[0]) {
case T_PUBLIC:
case T_PROTECTED:
case T_PRIVATE:
$promoted = true;
break;
case T_VARIABLE:
if ($promoted) {
$properties[] = ltrim($token[1], '$');
$promoted = false;
}
break;
}
}
}
return $properties;
}
}
+433
View File
@@ -0,0 +1,433 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi;
use OpenApi\Annotations as OA;
use OpenApi\Processors\ProcessorInterface;
/**
* Result of the analyser.
*
* Pretends to be an array of annotations, but also contains detected classes
* and helper functions for the processors.
*/
class Analysis
{
/**
* @var \SplObjectStorage
*/
public $annotations;
/**
* Class definitions.
*
* @var array
*/
public $classes = [];
/**
* Interface definitions.
*
* @var array
*/
public $interfaces = [];
/**
* Trait definitions.
*
* @var array
*/
public $traits = [];
/**
* Enum definitions.
*
* @var array
*/
public $enums = [];
/**
* The target OpenApi annotation.
*
* @var OA\OpenApi|null
*/
public $openapi = null;
/**
* @var Context|null
*/
public $context = null;
public function __construct(array $annotations = [], ?Context $context = null)
{
$this->annotations = new \SplObjectStorage();
$this->context = $context;
$this->addAnnotations($annotations, $context);
}
public function addAnnotation(object $annotation, Context $context): void
{
if ($this->annotations->contains($annotation)) {
return;
}
if ($annotation instanceof OA\OpenApi) {
$this->openapi = $this->openapi ?: $annotation;
} else {
if ($context->is('annotations') === false) {
$context->annotations = [];
}
if (in_array($annotation, $context->annotations, true) === false) {
$context->annotations[] = $annotation;
}
}
$this->annotations->attach($annotation, $context);
$blacklist = property_exists($annotation, '_blacklist') ? $annotation::$_blacklist : [];
foreach ($annotation as $property => $value) {
if (in_array($property, $blacklist)) {
if ($property === '_unmerged') {
foreach ($value as $item) {
$this->addAnnotation($item, $context);
}
}
} elseif (is_array($value)) {
foreach ($value as $item) {
if ($item instanceof OA\AbstractAnnotation) {
$this->addAnnotation($item, $context);
}
}
} elseif ($value instanceof OA\AbstractAnnotation) {
$this->addAnnotation($value, $context);
}
}
}
public function addAnnotations(array $annotations, Context $context): void
{
foreach ($annotations as $annotation) {
$this->addAnnotation($annotation, $context);
}
}
public function addClassDefinition(array $definition): void
{
$class = $definition['context']->fullyQualifiedName($definition['class']);
$this->classes[$class] = $definition;
}
public function addInterfaceDefinition(array $definition): void
{
$interface = $definition['context']->fullyQualifiedName($definition['interface']);
$this->interfaces[$interface] = $definition;
}
public function addTraitDefinition(array $definition): void
{
$trait = $definition['context']->fullyQualifiedName($definition['trait']);
$this->traits[$trait] = $definition;
}
public function addEnumDefinition(array $definition): void
{
$enum = $definition['context']->fullyQualifiedName($definition['enum']);
$this->enums[$enum] = $definition;
}
public function addAnalysis(Analysis $analysis): void
{
foreach ($analysis->annotations as $annotation) {
$this->addAnnotation($annotation, $analysis->annotations[$annotation]);
}
$this->classes = array_merge($this->classes, $analysis->classes);
$this->interfaces = array_merge($this->interfaces, $analysis->interfaces);
$this->traits = array_merge($this->traits, $analysis->traits);
$this->enums = array_merge($this->enums, $analysis->enums);
if ($this->openapi === null && $analysis->openapi !== null) {
$this->openapi = $analysis->openapi;
}
}
/**
* Get all subclasses of the given parent class.
*
* @param string $parent the parent class
*
* @return array map of class => definition pairs of sub-classes
*/
public function getSubClasses(string $parent): array
{
$definitions = [];
foreach ($this->classes as $class => $classDefinition) {
if ($classDefinition['extends'] === $parent) {
$definitions[$class] = $classDefinition;
$definitions = array_merge($definitions, $this->getSubClasses($class));
}
}
return $definitions;
}
/**
* Get a list of all super classes for the given class.
*
* @param string $class the class name
* @param bool $direct flag to find only the actual class parents
*
* @return array map of class => definition pairs of parent classes
*/
public function getSuperClasses(string $class, bool $direct = false): array
{
$classDefinition = $this->classes[$class] ?? null;
if (!$classDefinition || empty($classDefinition['extends'])) {
// unknown class, or no inheritance
return [];
}
$extends = $classDefinition['extends'];
$extendsDefinition = $this->classes[$extends] ?? null;
if (!$extendsDefinition) {
return [];
}
$parentDetails = [$extends => $extendsDefinition];
if ($direct) {
return $parentDetails;
}
return array_merge($parentDetails, $this->getSuperClasses($extends));
}
/**
* Get the list of interfaces used by the given class or by classes which it extends.
*
* @param string $class the class name
* @param bool $direct flag to find only the actual class interfaces
*
* @return array map of class => definition pairs of interfaces
*/
public function getInterfacesOfClass(string $class, bool $direct = false): array
{
$classes = $direct ? [] : array_keys($this->getSuperClasses($class));
// add self
$classes[] = $class;
$definitions = [];
foreach ($classes as $clazz) {
if (isset($this->classes[$clazz])) {
$definition = $this->classes[$clazz];
if (isset($definition['implements'])) {
foreach ($definition['implements'] as $interface) {
if (array_key_exists($interface, $this->interfaces)) {
$definitions[$interface] = $this->interfaces[$interface];
}
}
}
}
}
if (!$direct) {
// expand recursively for interfaces extending other interfaces
$collect = function ($interfaces, $cb) use (&$definitions): void {
foreach ($interfaces as $interface) {
if (isset($this->interfaces[$interface]['extends'])) {
$cb($this->interfaces[$interface]['extends'], $cb);
foreach ($this->interfaces[$interface]['extends'] as $fqdn) {
$definitions[$fqdn] = $this->interfaces[$fqdn];
}
}
}
};
$collect(array_keys($definitions), $collect);
}
return $definitions;
}
/**
* Get the list of traits used by the given class/trait or by classes which it extends.
*
* @param string $source the source name
* @param bool $direct flag to find only the actual class traits
*
* @return array map of class => definition pairs of traits
*/
public function getTraitsOfClass(string $source, bool $direct = false): array
{
$sources = $direct ? [] : array_keys($this->getSuperClasses($source));
// add self
$sources[] = $source;
$definitions = [];
foreach ($sources as $sourze) {
if (isset($this->classes[$sourze]) || isset($this->traits[$sourze])) {
$definition = $this->classes[$sourze] ?? $this->traits[$sourze];
if (isset($definition['traits'])) {
foreach ($definition['traits'] as $trait) {
if (array_key_exists($trait, $this->traits)) {
$definitions[$trait] = $this->traits[$trait];
}
}
}
}
}
if (!$direct) {
// expand recursively for traits using other traits
$collect = function ($traits, $cb) use (&$definitions): void {
foreach ($traits as $trait) {
if (isset($this->traits[$trait]['traits'])) {
$cb($this->traits[$trait]['traits'], $cb);
foreach ($this->traits[$trait]['traits'] as $fqdn) {
$definitions[$fqdn] = $this->traits[$fqdn];
}
}
}
};
$collect(array_keys($definitions), $collect);
}
return $definitions;
}
/**
* @param class-string|array<class-string> $classes one or more class names
* @param bool $strict in non-strict mode child classes are also detected
*
* @return OA\AbstractAnnotation[]
*/
public function getAnnotationsOfType($classes, bool $strict = false): array
{
$unique = new \SplObjectStorage();
$annotations = [];
foreach ((array) $classes as $class) {
/** @var OA\AbstractAnnotation $annotation */
foreach ($this->annotations as $annotation) {
if ($annotation instanceof $class && (!$strict || ($annotation->isRoot($class) && !$unique->contains($annotation)))) {
$unique->attach($annotation);
$annotations[] = $annotation;
}
}
}
return $annotations;
}
/**
* @param string $fqdn the source class/interface/trait
*/
public function getSchemaForSource(string $fqdn): ?OA\Schema
{
$fqdn = '\\' . ltrim($fqdn, '\\');
foreach ([$this->classes, $this->interfaces, $this->traits, $this->enums] as $definitions) {
if (array_key_exists($fqdn, $definitions)) {
$definition = $definitions[$fqdn];
if (is_iterable($definition['context']->annotations)) {
foreach (array_reverse($definition['context']->annotations) as $annotation) {
if ($annotation instanceof OA\Schema && $annotation->isRoot(OA\Schema::class) && !$annotation->_context->is('generated')) {
return $annotation;
}
}
}
}
}
return null;
}
public function getContext(object $annotation): ?Context
{
if ($annotation instanceof OA\AbstractAnnotation) {
return $annotation->_context;
}
if ($this->annotations->contains($annotation) === false) {
throw new \Exception('Annotation not found');
}
$context = $this->annotations[$annotation];
if ($context instanceof Context) {
return $context;
}
// Weird, did you use the addAnnotation/addAnnotations methods?
throw new \Exception('Annotation has no context');
}
/**
* Build an analysis with only the annotations that are merged into the OpenAPI annotation.
*/
public function merged(): Analysis
{
if ($this->openapi === null) {
throw new \Exception('No openapi target set. Run the MergeIntoOpenApi processor');
}
$unmerged = $this->openapi->_unmerged;
$this->openapi->_unmerged = [];
$analysis = new Analysis([$this->openapi], $this->context);
$this->openapi->_unmerged = $unmerged;
return $analysis;
}
/**
* Analysis with only the annotations that not merged.
*/
public function unmerged(): Analysis
{
return $this->split()->unmerged;
}
/**
* Split the annotation into two analysis.
* One with annotations that are merged and one with annotations that are not merged.
*
* @return \stdClass {merged: Analysis, unmerged: Analysis}
*/
public function split()
{
$result = new \stdClass();
$result->merged = $this->merged();
$result->unmerged = new Analysis([], $this->context);
foreach ($this->annotations as $annotation) {
if ($result->merged->annotations->contains($annotation) === false) {
$result->unmerged->annotations->attach($annotation, $this->annotations[$annotation]);
}
}
return $result;
}
/**
* Apply the processor(s).
*
* @param callable|ProcessorInterface|array<ProcessorInterface|callable> $processors One or more processors
*/
public function process($processors = null): void
{
if (is_array($processors) === false && is_callable($processors) || $processors instanceof ProcessorInterface) {
$processors = [$processors];
}
foreach ($processors as $processor) {
$processor($this);
}
}
public function validate(): bool
{
if ($this->openapi !== null) {
return $this->openapi->validate();
}
$this->context->logger->warning('No openapi target set. Run the MergeIntoOpenApi processor before validate()');
return false;
}
}
@@ -0,0 +1,795 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Context;
use OpenApi\Generator;
use OpenApi\Annotations as OA;
use OpenApi\Util;
use Symfony\Component\Yaml\Yaml;
/**
* The openapi annotation base class.
*/
abstract class AbstractAnnotation implements \JsonSerializable
{
/**
* While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.
* For further details see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specificationExtensions
* The keys inside the array will be prefixed with `x-`.
*
* @var array<string,mixed>
*/
public $x = Generator::UNDEFINED;
/**
* Arbitrary attachables for this annotation.
* These will be ignored but can be used for custom processing.
*
* @var array
*/
public $attachables = Generator::UNDEFINED;
/**
* @var Context|null
*/
public $_context = null;
/**
* Annotations that couldn't be merged by mapping or postprocessing.
*
* @var array
*/
public $_unmerged = [];
/**
* The properties which are required by [the spec](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md).
*
* @var array
*/
public static $_required = [];
/**
* Specify the type of the property.
*
* Examples:
* 'name' => 'string' // a string
* 'required' => 'boolean', // true or false
* 'tags' => '[string]', // array containing strings
* 'in' => ["query", "header", "path", "formData", "body"] // must be one on these
* 'oneOf' => [Schema::class] // array of schema objects.
*
* @var array<string,string|array<string>>
*/
public static $_types = [];
/**
* Declarative mapping of Annotation types to properties.
* Examples:
* Info::clas => 'info', // Set @OA\Info annotation as the info property.
* Parameter::clas => ['parameters'], // Append @OA\Parameter annotations the parameters array.
* PathItem::clas => ['paths', 'path'], // Append @OA\PathItem annotations the paths array and use path as key.
*
* @var array<class-string<AbstractAnnotation>,string|array<string>>
*/
public static $_nested = [];
/**
* Reverse mapping of $_nested with the allowed parent annotations.
*
* @var array<class-string<AbstractAnnotation>>
*/
public static $_parents = [];
/**
* List of properties are blacklisted from the JSON output.
*
* @var array<string>
*/
public static $_blacklist = ['_context', '_unmerged', '_analysis', 'attachables'];
public function __construct(array $properties)
{
if (isset($properties['_context'])) {
$this->_context = $properties['_context'];
unset($properties['_context']);
} elseif (Generator::$context) {
$this->_context = Generator::$context;
} else {
$this->_context = Context::detect(1);
}
if ($this->_context->is('annotations') === false) {
$this->_context->annotations = [];
}
$this->_context->annotations[] = $this;
$nestedContext = new Context(['nested' => $this], $this->_context);
foreach ($properties as $property => $value) {
if (property_exists($this, $property)) {
$this->{$property} = $value;
if (is_array($value)) {
foreach ($value as $key => $annotation) {
if ($annotation instanceof AbstractAnnotation) {
$this->{$property}[$key] = $this->nested($annotation, $nestedContext);
}
}
}
} elseif ($property !== 'value') {
$this->{$property} = $value;
} elseif (is_array($value)) {
$annotations = [];
foreach ($value as $annotation) {
if ($annotation instanceof AbstractAnnotation) {
$annotations[] = $annotation;
} else {
$this->_context->logger->warning('Unexpected field in ' . $this->identity() . ' in ' . $this->_context);
}
}
$this->merge($annotations);
} elseif (is_object($value)) {
$this->merge([$value]);
} else {
if (!Generator::isDefault($value)) {
$this->_context->logger->warning('Unexpected parameter "' . $property . '" in ' . $this->identity());
}
}
}
if ($this instanceof OpenApi) {
if ($this->_context->root()->version) {
// override via `Generator::setVersion()`
$this->openapi = $this->_context->root()->version;
} else {
$this->_context->root()->version = $this->openapi;
}
}
}
public function __get(string $property)
{
$properties = get_object_vars($this);
$this->_context->logger->warning('Property "' . $property . '" doesn\'t exist in a ' . $this->identity() . ', existing properties: "' . implode('", "', array_keys($properties)) . '" in ' . $this->_context);
}
/**
* @param mixed $value
*/
public function __set(string $property, $value): void
{
$fields = get_object_vars($this);
foreach (static::$_blacklist as $_property) {
unset($fields[$_property]);
}
$this->_context->logger->warning('Ignoring unexpected property "' . $property . '" for ' . $this->identity() . ', expecting "' . implode('", "', array_keys($fields)) . '" in ' . $this->_context);
}
/**
* Merge given annotations to their mapped properties configured in static::$_nested.
*
* Annotations that couldn't be merged are added to the _unmerged array.
*
* @param AbstractAnnotation[] $annotations
* @param bool $ignore Ignore unmerged annotations
*
* @return AbstractAnnotation[] The unmerged annotations
*/
public function merge(array $annotations, bool $ignore = false): array
{
$unmerged = [];
$nestedContext = new Context(['nested' => $this], $this->_context);
foreach ($annotations as $annotation) {
$mapped = false;
if ($details = $this->matchNested($annotation)) {
$property = $details->value;
if (is_array($property)) {
$property = $property[0];
if (Generator::isDefault($this->{$property})) {
$this->{$property} = [];
}
$this->{$property}[] = $this->nested($annotation, $nestedContext);
$mapped = true;
} elseif (Generator::isDefault($this->{$property})) {
// ignore duplicate nested if only one expected
$this->{$property} = $this->nested($annotation, $nestedContext);
$mapped = true;
}
}
if (!$mapped) {
$unmerged[] = $annotation;
}
}
if (!$ignore) {
foreach ($unmerged as $annotation) {
$this->_unmerged[] = $this->nested($annotation, $nestedContext);
}
}
return $unmerged;
}
/**
* Merge the properties from the given object into this annotation.
* Prevents overwriting properties that are already configured.
*
* @param object $object
*/
public function mergeProperties($object): void
{
$currentValues = get_object_vars($this);
foreach ($object as $property => $value) {
if ($property === '_context') {
continue;
}
if (Generator::isDefault($currentValues[$property])) {
// Overwrite default values
$this->{$property} = $value;
continue;
}
if ($property === '_unmerged') {
$this->_unmerged = array_merge($this->_unmerged, $value);
continue;
}
if ($currentValues[$property] !== $value) {
// New value is not the same?
if (Generator::isDefault($value)) {
continue;
}
$identity = method_exists($object, 'identity') ? $object->identity() : get_class($object);
$context1 = $this->_context;
$context2 = property_exists($object, '_context') ? $object->_context : 'unknown';
if (is_object($this->{$property}) && $this->{$property} instanceof AbstractAnnotation) {
$context1 = $this->{$property}->_context;
}
$this->_context->logger->error('Multiple definitions for ' . $identity . '->' . $property . "\n Using: " . $context1 . "\n Skipping: " . $context2);
}
}
}
/**
* Generate the documentation in YAML format.
*/
public function toYaml(?int $flags = null): string
{
if ($flags === null) {
$flags = Yaml::DUMP_OBJECT_AS_MAP ^ Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE;
}
return Yaml::dump(json_decode($this->toJson(JSON_INVALID_UTF8_IGNORE)), 10, 2, $flags);
}
/**
* Generate the documentation in JSON format.
*/
public function toJson(?int $flags = null): string
{
if ($flags === null) {
$flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_IGNORE;
}
return json_encode($this, $flags);
}
public function __debugInfo()
{
$properties = [];
foreach (get_object_vars($this) as $property => $value) {
if (!Generator::isDefault($value)) {
$properties[$property] = $value;
}
}
return $properties;
}
/**
* @return mixed
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
$data = new \stdClass();
// Strip undefined values.
foreach (get_object_vars($this) as $property => $value) {
if (!Generator::isDefault($value)) {
$data->{$property} = $value;
}
}
// Strip properties that are for internal (swagger-php) use.
foreach (static::$_blacklist as $property) {
unset($data->{$property});
}
// Correct empty array to empty objects.
foreach (static::$_types as $property => $type) {
if ($type === 'object' && is_array($data->{$property}) && empty($data->{$property})) {
$data->{$property} = new \stdClass();
}
}
// Inject vendor properties.
unset($data->x);
if (is_array($this->x)) {
foreach ($this->x as $property => $value) {
$prefixed = 'x-' . $property;
$data->{$prefixed} = $value;
}
}
// Map nested keys
foreach (static::$_nested as $nested) {
if (is_string($nested) || count($nested) === 1) {
continue;
}
$property = $nested[0];
if (Generator::isDefault($this->{$property})) {
continue;
}
$keyField = $nested[1];
$object = new \stdClass();
foreach ($this->{$property} as $key => $item) {
if (is_numeric($key) === false && is_array($item)) {
$object->{$key} = $item;
} else {
$key = $item->{$keyField};
if (!Generator::isDefault($key) && empty($object->{$key})) {
if ($item instanceof \JsonSerializable) {
$object->{$key} = $item->jsonSerialize();
} else {
$object->{$key} = $item;
}
unset($object->{$key}->{$keyField});
}
}
}
$data->{$property} = $object;
}
// $ref
if (isset($data->ref)) {
// Only specific https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#reference-object
$ref = ['$ref' => $data->ref];
if ($this->_context->version === OpenApi::VERSION_3_1_0) {
foreach (['summary', 'description'] as $prop) {
if (property_exists($this, $prop)) {
if (!Generator::isDefault($this->{$prop})) {
$ref[$prop] = $data->{$prop};
}
}
}
}
if (property_exists($this, 'nullable') && $this->nullable === true) {
$ref = ['oneOf' => [$ref]];
if ($this->_context->version == OpenApi::VERSION_3_1_0) {
$ref['oneOf'][] = ['type' => 'null'];
} else {
$ref['nullable'] = $data->nullable;
}
unset($data->nullable);
// preserve other properties
foreach (get_object_vars($this) as $property => $value) {
if ('_' == $property[0] || in_array($property, ['ref', 'nullable'])) {
continue;
}
if (!Generator::isDefault($value)) {
$ref[$property] = $value;
}
}
}
$data = (object) $ref;
}
if ($this->_context->version === OpenApi::VERSION_3_1_0) {
if (isset($data->nullable)) {
if (true === $data->nullable) {
if (isset($data->oneOf)) {
$data->oneOf[] = ['type' => 'null'];
} elseif (isset($data->anyOf)) {
$data->anyOf[] = ['type' => 'null'];
} elseif (isset($data->allOf)) {
$data->allOf[] = ['type' => 'null'];
} else {
$data->type = (array) $data->type;
$data->type[] = 'null';
}
}
unset($data->nullable);
}
if (isset($data->minimum) && isset($data->exclusiveMinimum)) {
if (true === $data->exclusiveMinimum) {
$data->exclusiveMinimum = $data->minimum;
unset($data->minimum);
} elseif (false === $data->exclusiveMinimum) {
unset($data->exclusiveMinimum);
}
}
if (isset($data->maximum) && isset($data->exclusiveMaximum)) {
if (true === $data->exclusiveMaximum) {
$data->exclusiveMaximum = $data->maximum;
unset($data->maximum);
} elseif (false === $data->exclusiveMaximum) {
unset($data->exclusiveMaximum);
}
}
}
return $data;
}
/**
* Validate annotation tree, and log notices & warnings.
*
* @param array $stack the path of annotations above this annotation in the tree
* @param array $skip (prevent stack overflow, when traversing an infinite dependency graph)
* @param string $ref Current ref path?
* @param object $context a free-form context contains
*/
public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool
{
if (in_array($this, $skip, true)) {
return true;
}
$valid = true;
// Report orphaned annotations
foreach ($this->_unmerged as $annotation) {
if (!is_object($annotation)) {
$this->_context->logger->warning('Unexpected type: "' . gettype($annotation) . '" in ' . $this->identity() . '->_unmerged, expecting a Annotation object');
break;
}
/** @var class-string<AbstractAnnotation> $class */
$class = get_class($annotation);
if ($details = $this->matchNested($annotation)) {
$property = $details->value;
if (is_array($property)) {
$this->_context->logger->warning('Only one ' . Util::shorten(get_class($annotation)) . '() allowed for ' . $this->identity() . ' multiple found, skipped: ' . $annotation->_context);
} else {
$this->_context->logger->warning('Only one ' . Util::shorten(get_class($annotation)) . '() allowed for ' . $this->identity() . " multiple found in:\n Using: " . $this->{$property}->_context . "\n Skipped: " . $annotation->_context);
}
} elseif ($annotation instanceof AbstractAnnotation) {
$message = 'Unexpected ' . $annotation->identity();
if ($class::$_parents) {
$message .= ', expected to be inside ' . implode(', ', Util::shorten($class::$_parents));
}
$this->_context->logger->warning($message . ' in ' . $annotation->_context);
}
$valid = false;
}
// Report conflicting key
foreach (static::$_nested as $annotationClass => $nested) {
if (is_string($nested) || count($nested) === 1) {
continue;
}
$property = $nested[0];
if (Generator::isDefault($this->{$property})) {
continue;
}
$keys = [];
$keyField = $nested[1];
foreach ($this->{$property} as $key => $item) {
if (is_array($item) && is_numeric($key) === false) {
$this->_context->logger->warning($this->identity() . '->' . $property . ' is an object literal, use nested ' . Util::shorten($annotationClass) . '() annotation(s) in ' . $this->_context);
$keys[$key] = $item;
} elseif (Generator::isDefault($item->{$keyField})) {
$this->_context->logger->error($item->identity() . ' is missing key-field: "' . $keyField . '" in ' . $item->_context);
} elseif (isset($keys[$item->{$keyField}])) {
$this->_context->logger->error('Multiple ' . $item->_identity([]) . ' with the same ' . $keyField . '="' . $item->{$keyField} . "\":\n " . $item->_context . "\n " . $keys[$item->{$keyField}]->_context);
} else {
$keys[$item->{$keyField}] = $item;
}
}
}
if (property_exists($this, 'ref') && !Generator::isDefault($this->ref) && is_string($this->ref)) {
if (substr($this->ref, 0, 2) === '#/' && count($stack) > 0 && $stack[0] instanceof OpenApi) {
// Internal reference
try {
$stack[0]->ref($this->ref);
} catch (\Exception $e) {
$this->_context->logger->warning($e->getMessage() . ' for ' . $this->identity() . ' in ' . $this->_context, ['exception' => $e]);
}
}
} else {
// Report missing required fields (when not a $ref)
foreach (static::$_required as $property) {
if (Generator::isDefault($this->{$property})) {
$message = 'Missing required field "' . $property . '" for ' . $this->identity() . ' in ' . $this->_context;
foreach (static::$_nested as $class => $nested) {
$nestedProperty = is_array($nested) ? $nested[0] : $nested;
if ($property === $nestedProperty) {
if ($this instanceof OpenApi) {
$message = 'Required ' . Util::shorten($class) . '() not found';
} elseif (is_array($nested)) {
$message = $this->identity() . ' requires at least one ' . Util::shorten($class) . '() in ' . $this->_context;
} else {
$message = $this->identity() . ' requires a ' . Util::shorten($class) . '() in ' . $this->_context;
}
break;
}
}
$this->_context->logger->warning($message);
}
}
}
// Report invalid types
foreach (static::$_types as $property => $type) {
$value = $this->{$property};
if (Generator::isDefault($value) || $value === null) {
continue;
}
if (is_string($type)) {
if ($this->validateType($type, $value) === false) {
$valid = false;
$this->_context->logger->warning($this->identity() . '->' . $property . ' is a "' . gettype($value) . '", expecting a "' . $type . '" in ' . $this->_context);
}
} elseif (is_array($type)) { // enum?
if (in_array($value, $type) === false) {
$this->_context->logger->warning($this->identity() . '->' . $property . ' "' . $value . '" is invalid, expecting "' . implode('", "', $type) . '" in ' . $this->_context);
}
} else {
throw new \Exception('Invalid ' . get_class($this) . '::$_types[' . $property . ']');
}
}
$stack[] = $this;
return self::_validate($this, $stack, $skip, $ref, $context) ? $valid : false;
}
/**
* Recursively validate all annotation properties.
*
* @param array|object $fields
*/
private static function _validate($fields, array $stack, array $skip, string $baseRef, ?object $context): bool
{
$valid = true;
$blacklist = [];
if (is_object($fields)) {
if (in_array($fields, $skip, true)) {
return true;
}
$skip[] = $fields;
$blacklist = property_exists($fields, '_blacklist') ? $fields::$_blacklist : [];
}
foreach ($fields as $field => $value) {
if ($value === null || is_scalar($value) || in_array($field, $blacklist)) {
continue;
}
$ref = $baseRef !== '' ? $baseRef . '/' . urlencode((string) $field) : urlencode((string) $field);
if (is_object($value)) {
if (method_exists($value, 'validate')) {
if (!$value->validate($stack, $skip, $ref, $context)) {
$valid = false;
}
} elseif (!self::_validate($value, $stack, $skip, $ref, $context)) {
$valid = false;
}
} elseif (is_array($value) && !self::_validate($value, $stack, $skip, $ref, $context)) {
$valid = false;
}
}
return $valid;
}
/**
* Return a identity for easy debugging.
* Example: "@OA\Get(path="/pets")".
*/
public function identity(): string
{
$class = get_class($this);
$properties = [];
/** @var class-string<AbstractAnnotation> $parent */
foreach (static::$_parents as $parent) {
foreach ($parent::$_nested as $annotationClass => $entry) {
if ($annotationClass === $class && is_array($entry) && !Generator::isDefault($this->{$entry[1]})) {
$properties[] = $entry[1];
break 2;
}
}
}
return $this->_identity($properties);
}
/**
* Check if `$other` can be nested and if so return details about where/how.
*
* @param AbstractAnnotation $other the other annotation
*
* @return null|object key/value object or `null`
*/
public function matchNested($other)
{
if ($other instanceof AbstractAnnotation && array_key_exists($root = $other->getRoot(), static::$_nested)) {
return (object) ['key' => $root, 'value' => static::$_nested[$root]];
}
return null;
}
/**
* Get the root annotation.
*
* This is used for resolving type equality and nesting rules to allow those rules to also work for custom,
* derived annotation classes.
*
* @return class-string the root annotation class in the `OpenApi\\Annotations` namespace
*/
public function getRoot(): string
{
$class = get_class($this);
do {
if (0 === strpos($class, 'OpenApi\\Annotations\\')) {
break;
}
} while ($class = get_parent_class($class));
return $class;
}
/**
* Match the annotation root.
*
* @param class-string $rootClass the root class to match
*/
public function isRoot(string $rootClass): bool
{
return $this->getRoot() == $rootClass;
}
/**
* Helper for generating the identity().
*/
protected function _identity(array $properties): string
{
$fields = [];
foreach ($properties as $property) {
$value = $this->{$property};
if ($value !== null && !Generator::isDefault($value)) {
$fields[] = $property . '=' . (is_string($value) ? '"' . $value . '"' : $value);
}
}
return Util::shorten(get_class($this)) . '(' . implode(',', $fields) . ')';
}
/**
* Validates the matching of the property value to a annotation type.
*
* @param string $type The annotations property type
* @param mixed $value The property value
*/
private function validateType(string $type, $value): bool
{
if (substr($type, 0, 1) === '[' && substr($type, -1) === ']') { // Array of a specified type?
if ($this->validateType('array', $value) === false) {
return false;
}
$itemType = substr($type, 1, -1);
foreach ($value as $i => $item) {
if ($this->validateType($itemType, $item) === false) {
return false;
}
}
return true;
}
if (is_subclass_of($type, AbstractAnnotation::class)) {
$type = 'object';
}
return $this->validateDefaultTypes($type, $value);
}
/**
* Validates default Open Api types.
*
* @param string $type The property type
* @param mixed $value The value to validate
*/
private function validateDefaultTypes(string $type, $value): bool
{
if (str_contains($type, '|')) {
$types = explode('|', $type);
foreach ($types as $type) {
if ($this->validateDefaultTypes($type, $value)) {
return true;
}
}
return false;
}
switch ($type) {
case 'string':
return is_string($value);
case 'boolean':
return is_bool($value);
case 'integer':
return is_int($value);
case 'number':
return is_numeric($value);
case 'object':
return is_object($value);
case 'array':
return $this->validateArrayType($value);
case 'scheme':
return in_array($value, ['http', 'https', 'ws', 'wss'], true);
default:
throw new \Exception('Invalid type "' . $type . '"');
}
}
/**
* Validate array type.
*
* @param mixed $value
*/
private function validateArrayType($value): bool
{
if (is_array($value) === false) {
return false;
}
$count = 0;
foreach ($value as $i => $item) {
// not a array, but a hash/map
if ($count !== $i) {
return false;
}
$count++;
}
return true;
}
/**
* Wrap the context with a reference to the annotation it is nested in.
*
* @param AbstractAnnotation $annotation
*
* @return AbstractAnnotation
*/
protected function nested(AbstractAnnotation $annotation, Context $nestedContext)
{
if (property_exists($annotation, '_context') && $annotation->_context === $this->_context) {
$annotation->_context = $nestedContext;
}
return $annotation;
}
protected function combine(...$args): array
{
$combined = [];
foreach ($args as $arg) {
if (is_array($arg)) {
$combined = array_merge($combined, $arg);
} else {
$combined[] = $arg;
}
}
return array_filter($combined, function ($value) {
return !Generator::isDefault($value) && $value !== null;
});
}
}
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* @Annotation
*/
class AdditionalProperties extends Schema
{
/**
* @inheritdoc
*/
public static $_parents = [
Schema::class,
Property::class,
Items::class,
JsonContent::class,
XmlContent::class,
AdditionalProperties::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Discriminator::class => 'discriminator',
Items::class => 'items',
Property::class => ['properties', 'property'],
ExternalDocumentation::class => 'externalDocs',
Xml::class => 'xml',
AdditionalProperties::class => 'additionalProperties',
Attachable::class => ['attachables'],
];
}
@@ -0,0 +1,74 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* A container for custom data to be attached to an annotation.
*
* These will be ignored by `swagger-php` but can be used for custom processing.
*
* @Annotation
*/
class Attachable extends AbstractAnnotation
{
/**
* @inheritdoc
*/
public static $_parents = [
AdditionalProperties::class,
Components::class,
Contact::class,
Delete::class,
Discriminator::class,
Examples::class,
ExternalDocumentation::class,
Flow::class,
Get::class,
Head::class,
Header::class,
Info::class,
Items::class,
JsonContent::class,
License::class,
Link::class,
MediaType::class,
OpenApi::class,
Operation::class,
Options::class,
Parameter::class,
Patch::class,
PathItem::class,
PathParameter::class,
Post::class,
Property::class,
Put::class,
RequestBody::class,
Response::class,
Schema::class,
SecurityScheme::class,
Server::class,
ServerVariable::class,
Tag::class,
Trace::class,
Webhook::class,
Xml::class,
XmlContent::class,
];
/**
* Allows to type-hint a specific parent annotation class.
*
* Container to allow custom annotations that are limited to a subset of potential parent
* annotation classes.
*
* @return array<class-string>|null List of valid parent annotation classes. If `null`, the default nesting rules apply.
*/
public function allowedParents(): ?array
{
return null;
}
}
@@ -0,0 +1,146 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
use OpenApi\Util;
/**
* Holds a set of reusable objects for different aspects of the OA.
*
* All objects defined within the components object will have no effect on the API unless they are explicitly
* referenced from properties outside the components object.
*
* @see [OAI Components Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#components-object)
*
* @Annotation
*/
class Components extends AbstractAnnotation
{
public const COMPONENTS_PREFIX = '#/components/';
/**
* Schema reference.
*
* @var string
*/
public const SCHEMA_REF = '#/components/schemas/';
/**
* Reusable Schemas.
*
* @var array<Schema|\OpenApi\Attributes\Schema>
*/
public $schemas = Generator::UNDEFINED;
/**
* Reusable Responses.
*
* @var Response[]
*/
public $responses = Generator::UNDEFINED;
/**
* Reusable Parameters.
*
* @var Parameter[]
*/
public $parameters = Generator::UNDEFINED;
/**
* Reusable Examples.
*
* @var Examples[]
*/
public $examples = Generator::UNDEFINED;
/**
* Reusable Request Bodies.
*
* @var RequestBody[]
*/
public $requestBodies = Generator::UNDEFINED;
/**
* Reusable Headers.
*
* @var Header[]
*/
public $headers = Generator::UNDEFINED;
/**
* Reusable Security Schemes.
*
* @var SecurityScheme[]
*/
public $securitySchemes = Generator::UNDEFINED;
/**
* Reusable Links.
*
* @var Link[]
*/
public $links = Generator::UNDEFINED;
/**
* Reusable Callbacks.
*
* @var array
*/
public $callbacks = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_parents = [
OpenApi::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Response::class => ['responses', 'response'],
Parameter::class => ['parameters', 'parameter'],
PathParameter::class => ['parameters', 'parameter'],
RequestBody::class => ['requestBodies', 'request'],
Examples::class => ['examples', 'example'],
Header::class => ['headers', 'header'],
SecurityScheme::class => ['securitySchemes', 'securityScheme'],
Link::class => ['links', 'link'],
Schema::class => ['schemas', 'schema'],
Attachable::class => ['attachables'],
];
/**
* Generate a `#/components/...` reference for the given annotation.
*
* A `string` component value always assumes type `Schema`.
*
* @param AbstractAnnotation|string $component
*/
public static function ref($component, bool $encode = true): string
{
if ($component instanceof AbstractAnnotation) {
foreach (Components::$_nested as $type => $nested) {
// exclude attachables
if (2 == count($nested)) {
if ($component instanceof $type) {
$type = $nested[0];
$name = $component->{$nested[1]};
break;
}
}
}
} else {
$type = 'schemas';
$name = $component;
}
return self::COMPONENTS_PREFIX . $type . '/' . ($encode ? Util::refEncode((string) $name) : $name);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Contact information for the exposed API.
*
* @see [OAI Contact Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#contact-object)
*
* @Annotation
*/
class Contact extends AbstractAnnotation
{
/**
* The identifying name of the contact person/organization.
*
* @var string
*/
public $name = Generator::UNDEFINED;
/**
* The URL pointing to the contact information.
*
* @var string
*/
public $url = Generator::UNDEFINED;
/**
* The email address of the contact person/organization.
*
* @var string
*/
public $email = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_types = [
'name' => 'string',
'url' => 'string',
'email' => 'string',
];
/**
* @inheritdoc
*/
public static $_parents = [
Info::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Attachable::class => ['attachables'],
];
}
@@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* A `@OA\Request` cookie parameter.
*
* @Annotation
*/
class CookieParameter extends Parameter
{
/**
* @inheritdoc
* This takes 'cookie' as the default location.
*/
public $in = 'cookie';
}
+25
View File
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* @Annotation
*/
class Delete extends Operation
{
/**
* @inheritdoc
*/
public $method = 'delete';
/**
* @inheritdoc
*/
public static $_parents = [
PathItem::class,
];
}
@@ -0,0 +1,69 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* The discriminator is a specific object in a schema which is used to inform the consumer of
* the specification of an alternative schema based on the value associated with it.
*
* This object is based on the [JSON Schema Specification](http://json-schema.org) and uses a predefined subset of it.
* On top of this subset, there are extensions provided by this specification to allow for more complete documentation.
*
* @see [OAI Discriminator Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#discriminatorObject)
* @see [JSON Schema](http://json-schema.org/)
*
* @Annotation
*/
class Discriminator extends AbstractAnnotation
{
/**
* The name of the property in the payload that will hold the discriminator value.
*
* @var string
*/
public $propertyName = Generator::UNDEFINED;
/**
* An object to hold mappings between payload values and schema names or references.
*
* @var string[]
*/
public $mapping = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['propertyName'];
/**
* @inheritdoc
*/
public static $_types = [
'propertyName' => 'string',
];
/**
* @inheritdoc
*/
public static $_parents = [
Schema::class,
Property::class,
AdditionalProperties::class,
Items::class,
JsonContent::class,
XmlContent::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Attachable::class => ['attachables'],
];
}
+98
View File
@@ -0,0 +1,98 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
*/
class Examples extends AbstractAnnotation
{
/**
* The relative or absolute path to an example.
*
* @see [Using refs](https://swagger.io/docs/specification/using-ref/)
*
* @var string|class-string|object
*/
public $ref = Generator::UNDEFINED;
/**
* The key into `#/components/examples`.
*
* @var string
*/
public $example = Generator::UNDEFINED;
/**
* Short description for the example.
*
* @var string
*/
public $summary = Generator::UNDEFINED;
/**
* Embedded literal example.
*
* The value field and externalValue field are mutually exclusive.
*
* To represent examples of media types that cannot naturally be represented
* in JSON or YAML, use a string value to contain the example, escaping where necessary.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* Embedded literal example.
*
* The value field and externalValue field are mutually exclusive.
*
* To represent examples of media types that cannot naturally be represented
* in JSON or YAML, use a string value to contain the example, escaping where necessary.
*
* @var int|string|array
*/
public $value = Generator::UNDEFINED;
/**
* An URL that points to the literal example.
*
* This provides the capability to reference examples that cannot easily be included
* in JSON or YAML documents.
*
* The value field and externalValue field are mutually exclusive.
*
* @var string
*/
public $externalValue = Generator::UNDEFINED;
public static $_types = [
'summary' => 'string',
'description' => 'string',
'externalValue' => 'string',
];
public static $_required = ['summary'];
public static $_parents = [
Components::class,
Parameter::class,
PathParameter::class,
MediaType::class,
JsonContent::class,
XmlContent::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Attachable::class => ['attachables'],
];
}
@@ -0,0 +1,76 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Allows referencing an external resource for extended documentation.
*
* @see [OAI External Documentation Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#external-documentation-object)
*
* @Annotation
*/
class ExternalDocumentation extends AbstractAnnotation
{
/**
* A short description of the target documentation. GFM syntax can be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* The URL for the target documentation.
*
* @var string
*/
public $url = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_types = [
'description' => 'string',
'url' => 'string',
];
/**
* @inheritdoc
*/
public static $_required = ['url'];
/**
* @inheritdoc
*/
public static $_parents = [
OpenApi::class,
Tag::class,
Schema::class,
AdditionalProperties::class,
Property::class,
Operation::class,
Get::class,
Post::class,
Put::class,
Delete::class,
Patch::class,
Head::class,
Options::class,
Trace::class,
Items::class,
JsonContent::class,
XmlContent::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Attachable::class => ['attachables'],
];
}
+106
View File
@@ -0,0 +1,106 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Configuration details for a supported OAuth Flow.
*
* @see [OAI OAuth Flow Object](https://swagger.io/specification/#oauthFlowObject)
*
* @Annotation
*/
class Flow extends AbstractAnnotation
{
/**
* The authorization url to be used for this flow.
*
* This must be in the form of an url.
*
* @var string
*/
public $authorizationUrl = Generator::UNDEFINED;
/**
* The token URL to be used for this flow.
*
* This must be in the form of an url.
*
* @var string
*/
public $tokenUrl = Generator::UNDEFINED;
/**
* The URL to be used for obtaining refresh tokens.
*
* This must be in the form of an url.
*
* @var string
*/
public $refreshUrl = Generator::UNDEFINED;
/**
* Flow name.
*
* One of ['implicit', 'password', 'authorizationCode', 'clientCredentials'].
*
* @var string
*/
public $flow = Generator::UNDEFINED;
/**
* The available scopes for the OAuth2 security scheme.
*
* A map between the scope name and a short description for it.
*
* @var array
*/
public $scopes = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['scopes', 'flow'];
/**
* @inheritdoc
*/
public static $_types = [
'flow' => ['implicit', 'password', 'authorizationCode', 'clientCredentials'],
'refreshUrl' => 'string',
'authorizationUrl' => 'string',
'tokenUrl' => 'string',
];
/**
* @inheritdoc
*/
public static $_parents = [
SecurityScheme::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
if (is_array($this->scopes) && empty($this->scopes)) {
$this->scopes = new \stdClass();
}
return parent::jsonSerialize();
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* @Annotation
*/
class Get extends Operation
{
/**
* @inheritdoc
*/
public $method = 'get';
/**
* @inheritdoc
*/
public static $_parents = [
PathItem::class,
];
}
+25
View File
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* @Annotation
*/
class Head extends Operation
{
/**
* @inheritdoc
*/
public $method = 'head';
/**
* @inheritdoc
*/
public static $_parents = [
PathItem::class,
];
}
+101
View File
@@ -0,0 +1,101 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @see [OAI Header Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#headerObject).
*
* @Annotation
*/
class Header extends AbstractAnnotation
{
/**
* The relative or absolute path to the endpoint.
*
* @see [Using refs](https://swagger.io/docs/specification/using-ref/)
*
* @var string|class-string|object
*/
public $ref = Generator::UNDEFINED;
/**
* @var string
*/
public $header = Generator::UNDEFINED;
/**
* A brief description of the parameter.
*
* This could contain examples of use.
* CommonMark syntax MAY be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* @var bool
*/
public $required = Generator::UNDEFINED;
/**
* Schema object.
*
* @var Schema
*/
public $schema = Generator::UNDEFINED;
/**
* Specifies that a parameter is deprecated and SHOULD be transitioned out of usage.
*
* @var bool
*/
public $deprecated = Generator::UNDEFINED;
/**
* Sets the ability to pass empty-valued parameters.
*
* This is valid only for query parameters and allows sending a parameter with an empty value.
*
* Default value is false.
*
* If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored.
*
* @var bool
*/
public $allowEmptyValue = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['header', 'schema'];
/**
* @inheritdoc
*/
public static $_types = [
'header' => 'string',
'description' => 'string',
];
/**
* @inheritdoc
*/
public static $_nested = [
Schema::class => 'schema',
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
Components::class,
Response::class,
];
}
@@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* A `@OA\Request` header parameter.
*
* @Annotation
*/
class HeaderParameter extends Parameter
{
/**
* @inheritdoc
* This takes 'header' as the default location.
*/
public $in = 'header';
}
+98
View File
@@ -0,0 +1,98 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* The object provides metadata about the API.
*
* The metadata may be used by the clients if needed and may be presented in editing or documentation generation tools for convenience.
*
* @see [OAI Info Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#info-object)
*
* @Annotation
*/
class Info extends AbstractAnnotation
{
/**
* The title of the application.
*
* @var string
*/
public $title = Generator::UNDEFINED;
/**
* A short description of the application.
*
* CommonMark syntax may be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* An URL to the Terms of Service for the API.
*
* Must be in the format of an url.
*
* @var string
*/
public $termsOfService = Generator::UNDEFINED;
/**
* The contact information for the exposed API.
*
* @var Contact
*/
public $contact = Generator::UNDEFINED;
/**
* The license information for the exposed API.
*
* @var License
*/
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 = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['title', 'version'];
/**
* @inheritdoc
*/
public static $_types = [
'title' => 'string',
'version' => 'string',
'description' => 'string',
'termsOfService' => 'string',
];
/**
* @inheritdoc
*/
public static $_nested = [
Contact::class => 'contact',
License::class => 'license',
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
OpenApi::class,
];
}
+62
View File
@@ -0,0 +1,62 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* The description of an item in a Schema with type `array`.
*
* @Annotation
*/
class Items extends Schema
{
/**
* @inheritdoc
*/
public static $_nested = [
Discriminator::class => 'discriminator',
Items::class => 'items',
Property::class => ['properties', 'property'],
ExternalDocumentation::class => 'externalDocs',
Xml::class => 'xml',
AdditionalProperties::class => 'additionalProperties',
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
Property::class,
AdditionalProperties::class,
Schema::class,
JsonContent::class,
XmlContent::class,
Items::class,
];
/**
* @inheritdoc
*/
public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool
{
if (in_array($this, $skip, true)) {
return true;
}
$valid = parent::validate($stack, $skip, $ref, $context);
$parent = end($stack);
if ($parent instanceof Schema && $parent->type !== 'array') {
$this->_context->logger->warning('@OA\\Items() parent type must be "array" in ' . $this->_context);
$valid = false;
}
// @todo Additional validation when used inside a Header or Parameter context.
return $valid;
}
}
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Shorthand for a json response.
*
* Use as `@OA\Schema` inside a `Response` and `MediaType`->`'application/json'` will be generated.
*
* @Annotation
*/
class JsonContent extends Schema
{
/**
* An associative array of Examples attributes.
*
* The keys represent the name of the example and the values are instances of the Examples attribute.
* Each example is used to show how the content of the request or response should look like.
*
* @var array<string,Examples>
*/
public $examples = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_parents = [];
/**
* @inheritdoc
*/
public static $_nested = [
Discriminator::class => 'discriminator',
Items::class => 'items',
Property::class => ['properties', 'property'],
ExternalDocumentation::class => 'externalDocs',
AdditionalProperties::class => 'additionalProperties',
Examples::class => ['examples', 'example'],
Attachable::class => ['attachables'],
];
}
+102
View File
@@ -0,0 +1,102 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* License information for the exposed API.
*
* @see [OAI License Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#license-object)
*
* @Annotation
*/
class License extends AbstractAnnotation
{
/**
* The license name used for the API.
*
* @var string
*/
public $name = Generator::UNDEFINED;
/**
* An SPDX license expression for the API. The `identifier` field is mutually exclusive of the `url` field.
*
* @var string
*/
public $identifier = Generator::UNDEFINED;
/**
* An URL to the license used for the API. This MUST be in the form of a URL.
*
* The `url` field is mutually exclusive of the `identifier` field.
*
* @var string
*/
public $url = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_types = [
'name' => 'string',
'identifier' => 'string',
'url' => 'string',
];
/**
* @inheritdoc
*/
public static $_required = ['name'];
/**
* @inheritdoc
*/
public static $_parents = [
Info::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
$data = parent::jsonSerialize();
if ($this->_context->isVersion(OpenApi::VERSION_3_0_0)) {
unset($data->identifier);
}
return $data;
}
/**
* @inheritdoc
*/
public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool
{
$valid = parent::validate($stack, $skip, $ref, $context);
if ($this->_context->isVersion(OpenApi::VERSION_3_1_0)) {
if (!Generator::isDefault($this->url) && $this->identifier !== Generator::UNDEFINED) {
$this->_context->logger->warning($this->identity() . ' url and identifier are mutually exclusive');
$valid = false;
}
}
return $valid;
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* The Link object represents a possible design-time link for a response.
*
* The presence of a link does not guarantee the caller's ability to successfully invoke it, rather it provides a known
* relationship and traversal mechanism between responses and other operations.
*
* Unlike dynamic links (i.e. links provided in the response payload), the OA linking mechanism does not require
* link information in the runtime response.
*
* For computing links, and providing instructions to execute them, a runtime expression is used for
* accessing values in an operation and using them as parameters while invoking the linked operation.
*
* @see [OAI Link Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#link-object)
*
* @Annotation
*/
class Link extends AbstractAnnotation
{
/**
* @see [Using refs](https://swagger.io/docs/specification/using-ref/)
*
* @var string|class-string|object
*/
public $ref = Generator::UNDEFINED;
/**
* The key into MediaType->links array.
*
* @var string
*/
public $link = Generator::UNDEFINED;
/**
* A relative or absolute reference to an OA operation.
*
* This field is mutually exclusive of the <code>operationId</code> field, and must point to an Operation object.
*
* Relative values may be used to locate an existing Operation object in the OpenAPI definition.
*
* @var string
*/
public $operationRef = Generator::UNDEFINED;
/**
* The name of an existing, resolvable OA operation, as defined with a unique <code>operationId</code>.
*
* This field is mutually exclusive of the <code>operationRef</code> field.
*
* @var string
*/
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).
*
* @var array<string,mixed>
*/
public $parameters = Generator::UNDEFINED;
/**
* A literal value or {expression} to use as a request body when calling the target operation.
*
* @var mixed
*/
public $requestBody = Generator::UNDEFINED;
/**
* A description of the link.
*
* CommonMark syntax may be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* A server object to be used by the target operation.
*
* @var Server
*/
public $server = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_nested = [
Server::class => 'server',
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
Components::class,
Response::class,
];
}
@@ -0,0 +1,88 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Each Media Type object provides schema and examples for the media type identified by its key.
*
* @see [OAI Media Type Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object)
*
* @Annotation
*/
class MediaType extends AbstractAnnotation
{
/**
* The key into Operation->content array.
*
* @var string
*/
public $mediaType = Generator::UNDEFINED;
/**
* The schema defining the type used for the request body.
*
* @var Schema
*/
public $schema = Generator::UNDEFINED;
/**
* Example of the media type.
*
* The example object should be in the correct format as specified by the media type.
* 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.
*
* @var mixed
*/
public $example = Generator::UNDEFINED;
/**
* Examples of the media type.
*
* Each example object should match the media type and specified schema if present.
* The examples object is mutually exclusive of the example object.
*
* Furthermore, if referencing a schema which contains an example,
* the examples value shall override the example provided by the schema.
*
* @var array<string,Examples>
*/
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.
*
* @var array<string,mixed>
*/
public $encoding = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_nested = [
Schema::class => 'schema',
Examples::class => ['examples', 'example'],
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
Response::class,
RequestBody::class,
];
}
+271
View File
@@ -0,0 +1,271 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Analysis;
use OpenApi\Generator;
use OpenApi\Util;
/**
* This is the root document object for the API specification.
*
* @see [OAI OpenApi Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#openapi-object)
*
* @Annotation
*/
class OpenApi extends AbstractAnnotation
{
public const VERSION_3_0_0 = '3.0.0';
public const VERSION_3_1_0 = '3.1.0';
public const DEFAULT_VERSION = self::VERSION_3_0_0;
public const SUPPORTED_VERSIONS = [self::VERSION_3_0_0, self::VERSION_3_1_0];
/**
* The semantic version number of the OpenAPI Specification version that the OpenAPI document uses.
*
* The openapi field should be used by tooling specifications and clients to interpret the OpenAPI document.
*
* A version specified via `Generator::setVersion()` will overwrite this value.
*
* This is not related to the API info::version string.
*
* @var string
*/
public $openapi = self::DEFAULT_VERSION;
/**
* Provides metadata about the API. The metadata may be used by tooling as required.
*
* @var Info
*/
public $info = Generator::UNDEFINED;
/**
* An array of <code>@Server</code> objects, which provide connectivity information to a target server.
*
* If not provided, or is an empty array, the default value would be a Server Object with an url value of <code>/</code>.
*
* @var Server[]
*/
public $servers = Generator::UNDEFINED;
/**
* The available paths and operations for the API.
*
* @var PathItem[]
*/
public $paths = Generator::UNDEFINED;
/**
* An element to hold various components for the specification.
*
* @var Components
*/
public $components = Generator::UNDEFINED;
/**
* A declaration of which security mechanisms can be used across the API.
*
* The list of values includes alternative security requirement objects that can be used.
* Only one of the security requirement objects need to be satisfied to authorize a request.
* Individual operations can override this definition.
* To make security optional, an empty security requirement `({})` can be included in the array.
*
* @var array
*/
public $security = Generator::UNDEFINED;
/**
* A list of tags used by the specification with additional metadata.
*
* The order of the tags can be used to reflect on their order by the parsing tools.
* Not all tags that are used by the Operation Object must be declared.
* The tags that are not declared may be organized randomly or based on the tools' logic.
* Each tag name in the list must be unique.
*
* @var Tag[]
*/
public $tags = Generator::UNDEFINED;
/**
* Additional external documentation.
*
* @var ExternalDocumentation
*/
public $externalDocs = Generator::UNDEFINED;
/**
* The available webhooks for the API.
*
* @var Webhook[]
*/
public $webhooks = Generator::UNDEFINED;
/**
* @var Analysis
*/
public $_analysis = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['openapi', 'info'];
/**
* @inheritdoc
*/
public static $_nested = [
Info::class => 'info',
Server::class => ['servers'],
PathItem::class => ['paths', 'path'],
Components::class => 'components',
Tag::class => ['tags'],
ExternalDocumentation::class => 'externalDocs',
Webhook::class => ['webhooks', 'webhook'],
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_types = [];
/**
* @inheritdoc
*/
public function validate(?array $stack = null, ?array $skip = null, string $ref = '', $context = null): bool
{
if ($stack !== null || $skip !== null || $ref !== '') {
$this->_context->logger->warning('Nested validation for ' . $this->identity() . ' not allowed');
return false;
}
if (!in_array($this->openapi, self::SUPPORTED_VERSIONS)) {
$this->_context->logger->warning('Unsupported OpenAPI version "' . $this->openapi . '". Allowed versions are: ' . implode(', ', self::SUPPORTED_VERSIONS));
return false;
}
/* paths is optional in 3.1.0 */
if ($this->openapi === self::VERSION_3_0_0 && Generator::isDefault($this->paths)) {
$this->_context->logger->warning('Required @OA\PathItem() not found');
}
if ($this->openapi === self::VERSION_3_1_0
&& Generator::isDefault($this->paths)
&& Generator::isDefault($this->webhooks)
&& Generator::isDefault($this->components)
) {
$this->_context->logger->warning("At least one of 'Required @OA\PathItem(), @OA\Components() or @OA\Webhook() not found'");
return false;
}
return parent::validate([], [], '#', new \stdClass());
}
/**
* Save the OpenAPI documentation to a file.
*/
public function saveAs(string $filename, string $format = 'auto'): void
{
if ($format === 'auto') {
$format = strtolower(substr($filename, -5)) === '.json' ? 'json' : 'yaml';
}
if (strtolower($format) === 'json') {
$content = $this->toJson();
} else {
$content = $this->toYaml();
}
if (file_put_contents($filename, $content) === false) {
throw new \Exception('Failed to saveAs("' . $filename . '", "' . $format . '")');
}
}
/**
* Look up an annotation with a $ref url.
*
* @param string $ref The $ref value, for example: "#/components/schemas/Product"
*/
public function ref(string $ref)
{
if (substr($ref, 0, 2) !== '#/') {
// @todo Add support for external (http) refs?
throw new \Exception('Unsupported $ref "' . $ref . '", it should start with "#/"');
}
return $this->resolveRef($ref, '#/', $this, []);
}
/**
* Recursive helper for ref().
*
* @param array|AbstractAnnotation $container
*/
private static function resolveRef(string $ref, string $resolved, $container, array $mapping)
{
if ($ref === $resolved) {
return $container;
}
$path = substr($ref, strlen($resolved));
$slash = strpos($path, '/');
$subpath = $slash === false ? $path : substr($path, 0, $slash);
$property = Util::refDecode($subpath);
$unresolved = $slash === false ? $resolved . $subpath : $resolved . $subpath . '/';
if (is_object($container)) {
if (property_exists($container, $property) === false) {
throw new \Exception('$ref "' . $ref . '" not found');
}
if ($slash === false) {
return $container->{$property};
}
$mapping = [];
if ($container instanceof AbstractAnnotation) {
foreach ($container::$_nested as $nestedClass => $nested) {
if (is_string($nested) === false && count($nested) === 2 && $nested[0] === $property) {
$mapping[$nestedClass] = $nested[1];
}
}
}
return self::resolveRef($ref, $unresolved, $container->{$property}, $mapping);
} elseif (is_array($container)) {
if (array_key_exists($property, $container)) {
return self::resolveRef($ref, $unresolved, $container[$property], []);
}
foreach ($mapping as $nestedClass => $keyField) {
foreach ($container as $key => $item) {
if (is_numeric($key) && is_object($item) && $item instanceof $nestedClass && (string) $item->{$keyField} === $property) {
return self::resolveRef($ref, $unresolved, $item, []);
}
}
}
}
throw new \Exception('$ref "' . $unresolved . '" not found');
}
/**
* @inheritdoc
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
$data = parent::jsonSerialize();
if (false === $this->_context->isVersion(OpenApi::VERSION_3_1_0)) {
unset($data->webhooks);
}
return $data;
}
}
+248
View File
@@ -0,0 +1,248 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Base class for `@OA\Get`, `@OA\Post`, `@OA\Put`, etc.
*
* Describes a single API operation on a path.
*
* @see [OAI Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#operation-object)
*
* @Annotation
*/
abstract class Operation extends AbstractAnnotation
{
/**
* Key in the OpenApi "Paths Object" for this operation.
*
* @var string
*/
public $path = Generator::UNDEFINED;
/**
* A list of tags for API documentation control.
*
* Tags can be used for logical grouping of operations by resources or any other qualifier.
*
* @var string[]
*/
public $tags = Generator::UNDEFINED;
/**
* Key in the OpenApi "Path Item Object" for this operation.
*
* Allowed values: 'get', 'post', put', 'patch', 'delete', 'options', 'head' and 'trace'.
*
* @var string
*/
public $method = Generator::UNDEFINED;
/**
* A short summary of what the operation does.
*
* @var string
*/
public $summary = Generator::UNDEFINED;
/**
* A verbose explanation of the operation behavior.
*
* CommonMark syntax MAY be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* Additional external documentation for this operation.
*
* @var ExternalDocumentation
*/
public $externalDocs = Generator::UNDEFINED;
/**
* Unique string used to identify the operation.
*
* The id must be unique among all operations described in the API.
* Tools and libraries may use the operationId to uniquely identify an operation, therefore, it is recommended to
* follow common programming naming conventions.
*
* @var string
*/
public $operationId = Generator::UNDEFINED;
/**
* A list of parameters that are applicable for this operation.
*
* If a parameter is already defined at the Path Item, the new definition will override it but can never remove it.
* The list must not include duplicated parameters.
*
* A unique parameter is defined by a combination of a name and location.
*
* The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object's
* components/parameters.
*
* @var Parameter[]
*/
public $parameters = Generator::UNDEFINED;
/**
* The request body applicable for this operation.
*
* The requestBody is only supported in HTTP methods where the HTTP 1.1 specification RFC7231 has explicitly
* defined semantics for request bodies. In other cases where the HTTP spec is vague, requestBody shall be ignored
* by consumers.
*
* @var RequestBody
*/
public $requestBody = Generator::UNDEFINED;
/**
* The list of possible responses as they are returned from executing this operation.
*
* @var Response[]
*/
public $responses = Generator::UNDEFINED;
/**
* A map of possible out-of band callbacks related to the parent operation.
*
* The key is a unique identifier for the Callback Object.
*
* Each value in the map is a Callback Object that describes a request that may be initiated by the API provider
* and the expected responses. The key value used to identify the callback object is an expression, evaluated at
* runtime, that identifies a URL to use for the callback operation.
*
* @var array
*/
public $callbacks = Generator::UNDEFINED;
/**
* Declares this operation to be deprecated.
*
* Consumers should refrain from usage of the declared operation.
*
* Default value is false.
*
* @var bool
*/
public $deprecated = Generator::UNDEFINED;
/**
* A declaration of which security mechanisms can be used for this operation.
*
* The list of values includes alternative security requirement objects that can be used.
*
* Only one of the security requirement objects need to be satisfied to authorize a request.
*
* This definition overrides any declared top-level security.
* To remove a top-level security declaration, an empty array can be used.
*
* @var array
*/
public $security = Generator::UNDEFINED;
/**
* An alternative server array to service this operation.
*
* If an alternative server object is specified at the Path Item Object or Root level, it will be overridden by
* this value.
*
* @var Server[]
*/
public $servers = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['responses'];
/**
* @inheritdoc
*/
public static $_types = [
'path' => 'string',
'method' => 'string',
'tags' => '[string]',
'summary' => 'string',
'description' => 'string',
'deprecated' => 'boolean',
];
/**
* @inheritdoc
*/
public static $_nested = [
Parameter::class => ['parameters'],
PathParameter::class => ['parameters'],
Response::class => ['responses', 'response'],
ExternalDocumentation::class => 'externalDocs',
Server::class => ['servers'],
RequestBody::class => 'requestBody',
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
$data = parent::jsonSerialize();
unset($data->method);
unset($data->path);
// ensure security elements are object
if (isset($data->security) && is_array($data->security)) {
foreach ($data->security as $key => $scheme) {
$data->security[$key] = (object) $scheme;
}
}
return $data;
}
/**
* @inheritdoc
*/
public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool
{
if (in_array($this, $skip, true)) {
return true;
}
$valid = parent::validate($stack, $skip, $ref, $context);
if (!Generator::isDefault($this->responses)) {
foreach ($this->responses as $response) {
if (!Generator::isDefault($response->response) && $response->response !== 'default' && preg_match('/^([12345]{1}[0-9]{2})|([12345]{1}XX)$/', (string) $response->response) === 0) {
$this->_context->logger->warning('Invalid value "' . $response->response . '" for ' . $response->_identity([]) . '->response, expecting "default", a HTTP Status Code or HTTP Status Code range definition in ' . $response->_context);
$valid = false;
}
}
}
if (is_object($context) && !Generator::isDefault($this->operationId)) {
if (!property_exists($context, 'operationIds')) {
$context->operationIds = [];
}
if (in_array($this->operationId, $context->operationIds)) {
$this->_context->logger->warning('operationId must be unique. Duplicate value found: "' . $this->operationId . '"');
$valid = false;
}
$context->operationIds[] = $this->operationId;
}
return $valid;
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* @Annotation
*/
class Options extends Operation
{
/**
* @inheritdoc
*/
public $method = 'options';
/**
* @inheritdoc
*/
public static $_parents = [
PathItem::class,
];
}
+301
View File
@@ -0,0 +1,301 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Describes a single operation parameter.
*
* A unique parameter is defined by a combination of a name and location.
*
* @see [OAA Parameter Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object)
*
* @Annotation
*/
class Parameter extends AbstractAnnotation
{
/**
* The relative or absolute path to the endpoint.
*
* @see [Using refs](https://swagger.io/docs/specification/using-ref/)
*
* @var string|class-string|object
*/
public $ref = Generator::UNDEFINED;
/**
* The key into <code>Components::parameters</code> or <code>PathItem::parameters</code> array.
*
* @var string
*/
public $parameter = Generator::UNDEFINED;
/**
* The (case-sensitive) name of the parameter.
*
* If in is "path", the name field must correspond to the associated path segment from the path field in the Paths Object.
*
* If in is "header" and the name field is "Accept", "Content-Type" or "Authorization", the parameter definition shall be ignored.
* For all other cases, the name corresponds to the parameter name used by the in property.
*
* @var string
*/
public $name = Generator::UNDEFINED;
/**
* The location of the parameter.
*
* Possible values are "query", "header", "path" or "cookie".
*
* @var string
*/
public $in = Generator::UNDEFINED;
/**
* A brief description of the parameter.
*
* This could contain examples of use.
*
* CommonMark syntax may be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* Determines whether this parameter is mandatory.
*
* If the parameter location is "path", this property is required and its value must be true.
* Otherwise, the property may be included and its default value is false.
*
* @var bool
*/
public $required = Generator::UNDEFINED;
/**
* Specifies that a parameter is deprecated and should be transitioned out of usage.
*
* @var bool
*/
public $deprecated = Generator::UNDEFINED;
/**
* Sets the ability to pass empty-valued parameters.
*
* This is valid only for query parameters and allows sending a parameter with an empty value.
*
* Default value is false.
*
* If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue shall be ignored.
*
* @var bool
*/
public $allowEmptyValue = Generator::UNDEFINED;
/**
* Describes how the parameter value will be serialized depending on the type of the parameter value.
*
* Default values (based on value of in): for query - form; for path - simple; for header - simple; for cookie - form.
*
* @var string
*/
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.
*
* For other types of parameters this property has no effect.
*
* When style is form, the default value is true.
* For all other styles, the default value is false.
*
* @var bool
*/
public $explode = Generator::UNDEFINED;
/**
* Determines whether the parameter value should allow reserved characters, as defined by RFC3986 :/?#[]@!$&'()*+,;= to be included without percent-encoding.
*
* This property only applies to parameters with an in value of query.
*
* The default value is false.
*
* @var bool
*/
public $allowReserved = Generator::UNDEFINED;
/**
* The schema defining the type used for the parameter.
*
* @var Schema
*/
public $schema = Generator::UNDEFINED;
/**
* Example of the media type.
*
* The example should match the specified schema and encoding properties if present.
* 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.
* 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.
*
* @var mixed
*/
public $example = Generator::UNDEFINED;
/**
* Examples of the media type.
*
* Each example should contain a value in the correct format as specified in the parameter encoding.
* The examples object is mutually exclusive of the example object.
* Furthermore, if referencing a schema which contains an example, the examples value shall override the example provided by the schema.
*
* @var array<string,Examples>
*/
public $examples = Generator::UNDEFINED;
/**
* A map containing the representations for the parameter.
*
* The key is the media type and the value describes it.
* The map must only contain one entry.
*
* @var array<MediaType>|JsonContent|XmlContent|Attachable
*/
public $content = Generator::UNDEFINED;
/**
* Path-style parameters defined by RFC6570.
*
* @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.7)
*/
public $matrix = Generator::UNDEFINED;
/**
* Label style parameters defined by RFC6570.
*
* @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.5)
*/
public $label = Generator::UNDEFINED;
/**
* Form style parameters defined by RFC6570.
*
* This option replaces collectionFormat with a csv (when explode is false) or multi (when explode is true) value from OpenAPI 2.0.
*
* @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.8)
*/
public $form = Generator::UNDEFINED;
/**
* Simple style parameters defined by RFC6570.
*
* This option replaces collectionFormat with a csv value from OpenAPI 2.0.
*
* @see [RFC6570](https://tools.ietf.org/html/rfc6570#section-3.2.2)
*
* @var array
*/
public $simple = Generator::UNDEFINED;
/**
* Space separated array values.
*
* This option replaces collectionFormat equal to ssv from OpenAPI 2.0.
*
* @var array
*/
public $spaceDelimited = Generator::UNDEFINED;
/**
* Pipe separated array values.
*
* This option replaces collectionFormat equal to pipes from OpenAPI 2.0.
*
* @var array
*/
public $pipeDelimited = Generator::UNDEFINED;
/**
* Provides a simple way of rendering nested objects using form parameters.
*/
public $deepObject = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['name', 'in'];
/**
* @inheritdoc
*/
public static $_types = [
'name' => 'string',
'in' => ['query', 'header', 'path', 'cookie'],
'description' => 'string',
'style' => ['matrix', 'label', 'form', 'simple', 'spaceDelimited', 'pipeDelimited', 'deepObject'],
'required' => 'boolean',
];
/**
* @inheritdoc
*/
public static $_nested = [
Schema::class => 'schema',
Examples::class => ['examples', 'example'],
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
Components::class,
PathItem::class,
Operation::class,
Get::class,
Post::class,
Put::class,
Delete::class,
Patch::class,
Head::class,
Options::class,
Trace::class,
];
/**
* @inheritdoc
*/
public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool
{
if (in_array($this, $skip, true)) {
return true;
}
$valid = parent::validate($stack, $skip, $ref, $context);
if (Generator::isDefault($this->ref)) {
if ($this->in === 'body') {
if (Generator::isDefault($this->schema)) {
$this->_context->logger->warning('Field "schema" is required when ' . $this->identity() . ' is in "' . $this->in . '" in ' . $this->_context);
$valid = false;
}
}
}
return $valid;
}
/**
* @inheritdoc
*/
public function identity(): string
{
return parent::_identity(['name', 'in']);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* @Annotation
*/
class Patch extends Operation
{
/**
* @inheritdoc
*/
public $method = 'patch';
/**
* @inheritdoc
*/
public static $_parents = [
PathItem::class,
];
}
+158
View File
@@ -0,0 +1,158 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Describes the operations available on a single path.
*
* A Path Item may be empty, due to ACL constraints.
* The path itself is still exposed to the documentation viewer, but they will not know which operations and parameters are available.
*
* @see [OAI Path Item Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#path-item-object)
*
* @Annotation
*/
class PathItem extends AbstractAnnotation
{
/**
* @see [Using refs](https://swagger.io/docs/specification/using-ref/)
*
* @var string|class-string|object
*/
public $ref = Generator::UNDEFINED;
/**
* An optional, string summary, intended to apply to all operations in this path.
*
* @var string
*/
public $summary = Generator::UNDEFINED;
/**
* An optional, string description, intended to apply to all operations in this path.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* Key for the Path Object (OpenApi->paths array).
*
* @var string
*/
public $path = Generator::UNDEFINED;
/**
* A definition of a GET operation on this path.
*
* @var Get
*/
public $get = Generator::UNDEFINED;
/**
* A definition of a PUT operation on this path.
*
* @var Put
*/
public $put = Generator::UNDEFINED;
/**
* A definition of a POST operation on this path.
*
* @var Post
*/
public $post = Generator::UNDEFINED;
/**
* A definition of a DELETE operation on this path.
*
* @var Delete
*/
public $delete = Generator::UNDEFINED;
/**
* A definition of a OPTIONS operation on this path.
*
* @var Options
*/
public $options = Generator::UNDEFINED;
/**
* A definition of a HEAD operation on this path.
*
* @var Head
*/
public $head = Generator::UNDEFINED;
/**
* A definition of a PATCH operation on this path.
*
* @var Patch
*/
public $patch = Generator::UNDEFINED;
/**
* A definition of a TRACE operation on this path.
*
* @var Trace
*/
public $trace = Generator::UNDEFINED;
/**
* An alternative server array to service all operations in this path.
*
* @var Server[]
*/
public $servers = Generator::UNDEFINED;
/**
* A list of parameters that are applicable for all the operations described under this path.
*
* These parameters can be overridden at the operation level, but cannot be removed there.
* The list must not include duplicated parameters.
* A unique parameter is defined by a combination of a name and location.
* The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object's components/parameters.
*
* @var Parameter[]
*/
public $parameters = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_types = [
'path' => 'string',
'summary' => 'string',
];
/**
* @inheritdoc
*/
public static $_nested = [
Get::class => 'get',
Post::class => 'post',
Put::class => 'put',
Delete::class => 'delete',
Patch::class => 'patch',
Trace::class => 'trace',
Head::class => 'head',
Options::class => 'options',
Parameter::class => ['parameters'],
PathParameter::class => ['parameters'],
Server::class => ['servers'],
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
OpenApi::class,
];
}
@@ -0,0 +1,26 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* A `@OA\Request` path parameter.
*
* @Annotation
*/
class PathParameter extends Parameter
{
/**
* @inheritdoc
* This takes 'path' as the default location.
*/
public $in = 'path';
/**
* @inheritdoc
*/
public $required = true;
}
+25
View File
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* @Annotation
*/
class Post extends Operation
{
/**
* @inheritdoc
*/
public $method = 'post';
/**
* @inheritdoc
*/
public static $_parents = [
PathItem::class,
];
}
+47
View File
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @Annotation
*/
class Property extends Schema
{
/**
* The key into Schema->properties array.
*
* @var string
*/
public $property = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_parents = [
AdditionalProperties::class,
Schema::class,
JsonContent::class,
XmlContent::class,
Property::class,
Items::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Discriminator::class => 'discriminator',
Items::class => 'items',
Property::class => ['properties', 'property'],
ExternalDocumentation::class => 'externalDocs',
Xml::class => 'xml',
AdditionalProperties::class => 'additionalProperties',
Attachable::class => ['attachables'],
];
}
+25
View File
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* @Annotation
*/
class Put extends Operation
{
/**
* @inheritdoc
*/
public $method = 'put';
/**
* @inheritdoc
*/
public static $_parents = [
PathItem::class,
];
}
@@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* A `@OA\Request` query parameter.
*
* @Annotation
*/
class QueryParameter extends Parameter
{
/**
* @inheritdoc
* This takes 'query' as the default location.
*/
public $in = 'query';
}
@@ -0,0 +1,98 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\XmlContent;
use OpenApi\Generator;
/**
* Describes a single request body.
*
* @see [OAI Request Body Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#requestBodyObject)
*
* @Annotation
*/
class RequestBody extends AbstractAnnotation
{
/**
* The relative or absolute path to a request body.
*
* @see [Using refs](https://swagger.io/docs/specification/using-ref/)
*
* @var string|class-string|object
*/
public $ref = Generator::UNDEFINED;
/**
* Request body model name.
*
* @var string
*/
public $request = Generator::UNDEFINED;
/**
* A brief description of the parameter.
*
* This could contain examples of use.
*
* CommonMark syntax may be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* Determines whether this parameter is mandatory.
*
* If the parameter location is "path", this property is required and its value must be true.
* Otherwise, the property may be included and its default value is false.
*
* @var bool
*/
public $required = Generator::UNDEFINED;
/**
* The content of the request body.
*
* The key is a media type or media type range and the value describes it. For requests that match multiple keys,
* only the most specific key is applicable. e.g. text/plain overrides text/*.
*
* @var array<MediaType|JsonContent|XmlContent>|MediaType|JsonContent|XmlContent|Attachable
*/
public $content = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_types = [
'description' => 'string',
'required' => 'boolean',
'request' => 'string',
];
public static $_parents = [
Components::class,
Delete::class,
Get::class,
Head::class,
Operation::class,
Options::class,
Patch::class,
Post::class,
Trace::class,
Put::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
MediaType::class => ['content', 'mediaType'],
Attachable::class => ['attachables'],
];
}
+130
View File
@@ -0,0 +1,130 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Describes a single response from an API Operation, including design-time,
* static links to operations based on the response.
*
* @see [OAI Response Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#response-object)
*
* @Annotation
*/
class Response extends AbstractAnnotation
{
/**
* The relative or absolute path to a response.
*
* @see [Using refs](https://swagger.io/docs/specification/using-ref/)
*
* @var string|class-string|object
*/
public $ref = Generator::UNDEFINED;
/**
* The key into Operations->responses array.
*
* A HTTP status code or <code>default</code>.
*
* @var string|int
*/
public $response = Generator::UNDEFINED;
/**
* A short description of the response.
*
* CommonMark syntax may be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* Maps a header name to its definition.
*
* RFC7230 states header names are case insensitive.
*
* If a response header is defined with the name "Content-Type", it shall be ignored.
*
* @see [RFC7230](https://tools.ietf.org/html/rfc7230#page-22)
*
* @var Header[]
*/
public $headers = Generator::UNDEFINED;
/**
* A map containing descriptions of potential response payloads.
*
* The key is a media type or media type range and the value describes it.
*
* For responses that match multiple keys, only the most specific key is applicable;
* e.g. <code>text/plain</code> overrides <code>text/*</code>.
*
* @var MediaType|JsonContent|XmlContent|Attachable|array<MediaType|JsonContent|XmlContent|Attachable>
*/
public $content = Generator::UNDEFINED;
/**
* A map of operations links that can be followed from the response.
*
* The key of the map is a short name for the link, following the naming constraints of the names for Component
* Objects.
*
* @var Link[]
*/
public $links = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_types = [
'description' => 'string',
];
/**
* @inheritdoc
*/
public static $_nested = [
MediaType::class => ['content', 'mediaType'],
Header::class => ['headers', 'header'],
Link::class => ['links', 'link'],
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
Components::class,
Operation::class,
Get::class,
Post::class,
Put::class,
Patch::class,
Delete::class,
Head::class,
Options::class,
Trace::class,
];
/**
* @inheritdoc
*/
public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool
{
$valid = parent::validate($stack, $skip, $ref, $context);
if (Generator::isDefault($this->description) && Generator::isDefault($this->ref)) {
$this->_context->logger->warning($this->identity() . ' One of description or ref is required');
$valid = false;
}
return $valid;
}
}
+495
View File
@@ -0,0 +1,495 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* The definition of input and output data types.
*
* These types can be objects, but also primitives and arrays.
*
* This object is based on the [JSON Schema Specification](http://json-schema.org) and uses a predefined subset of it.
* On top of this subset, there are extensions provided by this specification to allow for more complete documentation.
*
* @see [OAI Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schemaObject)
* @see [JSON Schema](http://json-schema.org/)
*
* @Annotation
*/
class Schema extends AbstractAnnotation
{
/**
* The relative or absolute path to the endpoint.
*
* @see [Using refs](https://swagger.io/docs/specification/using-ref/)
*
* @var string|class-string|object
*/
public $ref = Generator::UNDEFINED;
/**
* The key into Components->schemas array.
*
* @var string
*/
public $schema = Generator::UNDEFINED;
/**
* Can be used to decorate a user interface with information about the data produced by this user interface.
*
* Preferably short; use <code>description</code> for more details.
*
* @var string
*/
public $title = Generator::UNDEFINED;
/**
* A description will provide explanation about the purpose of the instance described by this schema.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* The maximum number of properties allowed in an object instance.
* An object instance is valid against this property if its number of properties is less than, or equal to, the value of this attribute.
*
* @var int
*/
public $maxProperties = Generator::UNDEFINED;
/**
* The minimum number of properties allowed in an object instance.
* An object instance is valid against this property if its number of properties is greater than, or equal to, the value of this attribute.
*
* @var int
*/
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 = Generator::UNDEFINED;
/**
* A collection of properties to define for an object.
*
* Each property is represented as an instance of the <a href="#property">Property</a> class.
*
* @var Property[]
*/
public $properties = Generator::UNDEFINED;
/**
* The type of the schema/property.
*
* OpenApi v3.0: The value MUST be one of "string", "number", "integer", "boolean", "array" or "object".
*
* Since OpenApi v3.1 an array of types may be used.
*
* @var string|non-empty-array<string>
*/
public $type = Generator::UNDEFINED;
/**
* The extending format for the previously mentioned type. See Data Type Formats for further details.
*
* @var string
*/
public $format = Generator::UNDEFINED;
/**
* Required if type is "array". Describes the type of items in the array.
*
* @var Items
*/
public $items = Generator::UNDEFINED;
/**
* 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 of type <code>query</code> or <code>formData</code>.
* Default value is csv.
*
* @var string
*/
public $collectionFormat = Generator::UNDEFINED;
/**
* Sets a default value to the parameter. The type of the value depends on the defined type.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor101)
*
* @var mixed
*/
public $default = Generator::UNDEFINED;
/**
* The maximum value allowed for a numeric property. This value must be a number.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor17)
*
* @var int|float
*/
public $maximum = Generator::UNDEFINED;
/**
* A boolean indicating whether the maximum value is excluded from the set of valid values.
*
* When set to true, the maximum value is excluded, and when false or not specified, it is included.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor17)
*
* @var bool|int|float
*/
public $exclusiveMaximum = Generator::UNDEFINED;
/**
* The minimum value allowed for a numeric property. This value must be a number.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor21)
*
* @var int|float
*/
public $minimum = Generator::UNDEFINED;
/**
* A boolean indicating whether the minimum value is excluded from the set of valid values.
*
* When set to true, the minimum value is excluded, and when false or not specified, it is included.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor21)
*
* @var bool|int|float
*/
public $exclusiveMinimum = Generator::UNDEFINED;
/**
* The maximum length of a string property.
*
* A string instance is valid against this property if its length is less than, or equal to, the value of this attribute.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor26)
*
* @var int
*/
public $maxLength = Generator::UNDEFINED;
/**
* The minimum length of a string property.
*
* A string instance is valid against this property if its length is greater than, or equal to, the value of this attribute.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor29)
*
* @var int
*/
public $minLength = Generator::UNDEFINED;
/**
* A string instance is considered valid if the regular expression matches the instance successfully.
*
* @var string
*/
public $pattern = Generator::UNDEFINED;
/**
* The maximum number of items allowed in an array property.
*
* An array instance is valid against this property if its number of items is less than, or equal to, the value of this attribute.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor42)
*
* @var int
*/
public $maxItems = Generator::UNDEFINED;
/**
* The minimum number of items allowed in an array property.
*
* An array instance is valid against this property if its number of items is greater than, or equal to, the value of this attribute.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor45)
*
* @var int
*/
public $minItems = Generator::UNDEFINED;
/**
* A boolean value indicating whether all items in an array property must be unique.
*
* If this attribute is set to true, then all items in the array must be unique.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor49)
*
* @var bool
*/
public $uniqueItems = Generator::UNDEFINED;
/**
* A collection of allowable values for a property.
*
* A property instance is valid against this attribute if its value is one of the values specified in this collection.
*
* @see [JSON schema validation](http://json-schema.org/latest/json-schema-validation.html#anchor76)
*
* @var string[]|int[]|float[]|bool[]|\UnitEnum[]|class-string
*/
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 int|float
*/
public $multipleOf = Generator::UNDEFINED;
/**
* Adds support for polymorphism.
*
* The discriminator is an object name that is used to differentiate between other schemas which may satisfy the
* payload description. See Composition and Inheritance for more details.
*
* @var Discriminator
*/
public $discriminator = Generator::UNDEFINED;
/**
* Declares the property as "read only".
*
* Relevant only for Schema "properties" definitions.
*
* This means that it may be sent as part of a response but should not be sent as part of the request.
* If the property is marked as readOnly being true and is in the required list, the required will take effect on
* the response only. A property must not be marked as both readOnly and writeOnly being true. Default value is
* false.
*
* @var bool
*/
public $readOnly = Generator::UNDEFINED;
/**
* Declares the property as "write only".
*
* Relevant only for Schema "properties" definitions.
* Therefore, it may be sent as part of a request but should not be sent as part of the response.
* If the property is marked as writeOnly being true and is in the required list, the required will take effect on
* the request only. A property must not be marked as both readOnly and writeOnly being true. Default value is
* false.
*
* @var bool
*/
public $writeOnly = Generator::UNDEFINED;
/**
* This may be used only on properties schemas.
*
* It has no effect on root schemas.
* Adds additional metadata to describe the XML representation of this property.
*
* @var Xml
*/
public $xml = Generator::UNDEFINED;
/**
* Additional external documentation for this schema.
*
* @var ExternalDocumentation
*/
public $externalDocs = Generator::UNDEFINED;
/**
* A free-form property to include an example of an instance for this schema.
*
* To represent examples that cannot naturally be represented in JSON or YAML, a string value can be used to
* contain the example with escaping where necessary.
*
* @var mixed
*/
public $example = Generator::UNDEFINED;
/**
* Allows sending a null value for the defined schema.
* Default value is false.
*
* This must not be used when using OpenApi version 3.1,
* instead make the "type" property an array and add "null" as a possible type.
*
* @var bool
*
* @see https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0
*/
public $nullable = Generator::UNDEFINED;
/**
* Specifies that a schema is deprecated and should be transitioned out of usage.
* Default value is false.
*
* @var bool
*/
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 array<Schema|\OpenApi\Attributes\Schema>
*/
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 array<Schema|\OpenApi\Attributes\Schema>
*/
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 array<Schema|\OpenApi\Attributes\Schema>
*/
public $oneOf = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.29.
*/
public $not = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#anchor64.
*
* @var bool|AdditionalProperties
*/
public $additionalProperties = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.10.
*/
public $additionalItems = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.14.
*/
public $contains = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.19.
*/
public $patternProperties = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.21.
*/
public $dependencies = Generator::UNDEFINED;
/**
* http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.22.
*/
public $propertyNames = Generator::UNDEFINED;
/**
* http://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.1.3.
*
* @var mixed
*/
public $const = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_types = [
'title' => 'string',
'description' => 'string',
'required' => '[string]',
'format' => 'string',
'collectionFormat' => ['csv', 'ssv', 'tsv', 'pipes', 'multi'],
'maximum' => 'number',
'exclusiveMaximum' => 'boolean|integer|number',
'minimum' => 'number',
'exclusiveMinimum' => 'boolean|integer|number',
'maxLength' => 'integer',
'minLength' => 'integer',
'pattern' => 'string',
'maxItems' => 'integer',
'minItems' => 'integer',
'uniqueItems' => 'boolean',
'multipleOf' => 'integer',
'allOf' => '[' . Schema::class . ']',
'oneOf' => '[' . Schema::class . ']',
'anyOf' => '[' . Schema::class . ']',
];
/**
* @inheritdoc
*/
public static $_nested = [
Discriminator::class => 'discriminator',
Items::class => 'items',
Property::class => ['properties', 'property'],
ExternalDocumentation::class => 'externalDocs',
Xml::class => 'xml',
AdditionalProperties::class => 'additionalProperties',
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
Components::class,
Parameter::class,
PathParameter::class,
MediaType::class,
Header::class,
];
/**
* @inheritdoc
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
$data = parent::jsonSerialize();
if (isset($data->const)) {
if ($this->_context->isVersion(OpenApi::VERSION_3_0_0)) {
$data->enum = [$data->const];
unset($data->const);
}
}
return $data;
}
/**
* @inheritdoc
*/
public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool
{
if ($this->type === 'array' && Generator::isDefault($this->items)) {
$this->_context->logger->warning('@OA\\Items() is required when ' . $this->identity() . ' has type "array" in ' . $this->_context);
return false;
}
return parent::validate($stack, $skip, $ref, $context);
}
}
@@ -0,0 +1,138 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @see [OAI Security Scheme Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#securitySchemeObject).
*
* @Annotation
*/
class SecurityScheme extends AbstractAnnotation
{
/**
* The relative or absolute path to a security scheme.
*
* @see [Using refs](https://swagger.io/docs/specification/using-ref/)
*
* @var string|class-string|object
*/
public $ref = Generator::UNDEFINED;
/**
* The key into OpenApi->security array.
*
* @var string
*/
public $securityScheme = Generator::UNDEFINED;
/**
* The type of the security scheme.
*
* @var string|non-empty-array<string>
*/
public $type = Generator::UNDEFINED;
/**
* A short description for security scheme.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* The name of the header or query parameter to be used.
*
* @var string
*/
public $name = Generator::UNDEFINED;
/**
* Required The location of the API key.
*
* @var string
*/
public $in = Generator::UNDEFINED;
/**
* The flow used by the OAuth2 security scheme.
*
* @var Flow[]
*/
public $flows = Generator::UNDEFINED;
/**
* A hint to the client to identify how the bearer token is formatted.
*
* Bearer tokens are usually generated by an authorization server, so this information is primarily for documentation purposes.
*
* @var string
*/
public $bearerFormat = Generator::UNDEFINED;
/**
* The name of the HTTP Authorization scheme.
*
* @see [RFC7235](https://tools.ietf.org/html/rfc7235#section-5.1)
*
* @var string
*/
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 = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['securityScheme', 'type'];
/**
* @inheritdoc
*/
public static $_types = [
'type' => ['http', 'apiKey', 'oauth2', 'openIdConnect'],
'description' => 'string',
'name' => 'string',
'bearerFormat' => 'string',
'in' => ['query', 'header', 'cookie'],
];
/**
* @inheritdoc
*/
public static $_nested = [
Flow::class => ['flows', 'flow'],
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_parents = [
Components::class,
];
/**
* @inheritdoc
*/
public function merge(array $annotations, bool $ignore = false): array
{
$unmerged = parent::merge($annotations, $ignore);
if ($this->type === 'oauth2') {
$this->name = Generator::UNDEFINED;
}
return $unmerged;
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* An object representing a server.
*
* @see [OAI Server Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#server-object)
*
* @Annotation
*/
class Server extends AbstractAnnotation
{
/**
* An URL to the target host.
*
* This URL supports Server Variables and may be relative,
* to indicate that the host location is relative to the location where the OpenAPI document is being served.
* Variable substitutions will be made when a variable is named in {brackets}.
*
* @var string
*/
public $url = Generator::UNDEFINED;
/**
* An optional string describing the host designated by the URL.
*
* CommonMark syntax may be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* A map between a variable name and its value.
*
* The value is used for substitution in the server's URL template.
*
* @var ServerVariable[]
*/
public $variables = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_parents = [
OpenApi::class,
PathItem::class,
Operation::class,
Get::class,
Post::class,
Put::class,
Delete::class,
Patch::class,
Head::class,
Options::class,
Trace::class,
Link::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
ServerVariable::class => ['variables', 'serverVariable'],
Attachable::class => ['attachables'],
];
/**
* @inheritdoc
*/
public static $_required = ['url'];
/**
* @inheritdoc
*/
public static $_types = [
'url' => 'string',
'description' => 'string',
];
}
@@ -0,0 +1,87 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* An object representing a server variable for server URL template substitution.
*
* @see [OAI Server Variable Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#server-variable-object)
*
* @Annotation
*/
class ServerVariable extends AbstractAnnotation
{
/**
* The key into Server->variables array.
*
* @var string
*/
public $serverVariable = Generator::UNDEFINED;
/**
* An enumeration of values to be used if the substitution options are from a limited set.
*
* @var string[]|int[]|float[]|bool[]|\UnitEnum[]|class-string
*/
public $enum = Generator::UNDEFINED;
/**
* The default value to use for substitution, and to send, if an alternate value is not supplied.
*
* Unlike the Schema Object's default, this value must be provided by the consumer.
*
* @var string
*/
public $default = Generator::UNDEFINED;
/**
* A map between a variable name and its value.
*
* The value is used for substitution in the server's URL template.
*
* @var array
*/
public $variables = Generator::UNDEFINED;
/**
* An optional description for the server variable.
*
* CommonMark syntax MAY be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_parents = [
Server::class,
];
/**
* @inheritdoc
*/
public static $_required = ['default'];
/**
* @inheritdoc
*/
public static $_types = [
'default' => 'string',
'description' => 'string',
];
/**
* @inheritdoc
*/
public static $_nested = [
Attachable::class => ['attachables'],
];
}
+66
View File
@@ -0,0 +1,66 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @see [OAI Tag Object]( https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#tagObject).
*
* @Annotation
*/
class Tag extends AbstractAnnotation
{
/**
* The name of the tag.
*
* @var string
*/
public $name = Generator::UNDEFINED;
/**
* A short description for the tag. GFM syntax can be used for rich text representation.
*
* @var string
*/
public $description = Generator::UNDEFINED;
/**
* Additional external documentation for this tag.
*
* @var ExternalDocumentation
*/
public $externalDocs = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['name'];
/**
* @inheritdoc
*/
public static $_types = [
'name' => 'string',
'description' => 'string',
];
/**
* @inheritdoc
*/
public static $_parents = [
OpenApi::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
ExternalDocumentation::class => 'externalDocs',
Attachable::class => ['attachables'],
];
}
+25
View File
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
/**
* @Annotation
*/
class Trace extends Operation
{
/**
* @inheritdoc
*/
public $method = 'trace';
/**
* @inheritdoc
*/
public static $_parents = [
PathItem::class,
];
}
+43
View File
@@ -0,0 +1,43 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Acts like a `PathItem` with the main difference being that it requires `webhook` instead of `path`.
*
* @Annotation
*/
class Webhook extends PathItem
{
/**
* Key for the webhooks map.
*
* @var string
*/
public $webhook = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_required = ['webhook'];
/**
* @inheritdoc
*/
public static $_parents = [
OpenApi::class,
];
/**
* @inheritdoc
*/
public static $_types = [
'webhook' => 'string',
];
}
+95
View File
@@ -0,0 +1,95 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* @see [OAI XML Object](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#xmlObject).
*
* @Annotation
*/
class Xml extends AbstractAnnotation
{
/**
* Replaces the name of the element/attribute used for the described schema property.
*
* When defined within the Items Object (items), it will affect the name of the individual XML elements within the list.
* When defined alongside type being array (outside the items), it will affect the wrapping element
* and only if wrapped is <code>true</code>.
*
* If wrapped is <code>false</code>, it will be ignored.
*
* @var string
*/
public $name = Generator::UNDEFINED;
/**
* The URL of the namespace definition. Value SHOULD be in the form of a URL.
*
* @var string
*/
public $namespace = Generator::UNDEFINED;
/**
* The prefix to be used for the name.
*
* @var string
*/
public $prefix = Generator::UNDEFINED;
/**
* Declares whether the property definition translates to an attribute instead of an element.
*
* Default value is <code>false</code>.
*
* @var bool
*/
public $attribute = Generator::UNDEFINED;
/**
* MAY be used only for an array definition.
*
* Signifies whether the array is wrapped (for example <code>&lt;books>&lt;book/>&lt;book/>&lt;/books></code>)
* or unwrapped (<code>&lt;book/>&lt;book/></code>).
*
* Default value is false. The definition takes effect only when defined alongside type being array (outside the items).
*
* @var bool
*/
public $wrapped = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_types = [
'name' => 'string',
'namespace' => 'string',
'prefix' => 'string',
'attribute' => 'boolean',
'wrapped' => 'boolean',
];
/**
* @inheritdoc
*/
public static $_parents = [
AdditionalProperties::class,
Schema::class,
Property::class,
Schema::class,
Items::class,
XmlContent::class,
];
/**
* @inheritdoc
*/
public static $_nested = [
Attachable::class => ['attachables'],
];
}
@@ -0,0 +1,43 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Annotations;
use OpenApi\Generator;
/**
* Shorthand for a xml response.
*
* Use as `@OA\Schema` inside a `Response` and `MediaType`->`'application/xml'` will be generated.
*
* @Annotation
*/
class XmlContent extends Schema
{
/**
* @var array<string,Examples>
*/
public $examples = Generator::UNDEFINED;
/**
* @inheritdoc
*/
public static $_parents = [];
/**
* @inheritdoc
*/
public static $_nested = [
Discriminator::class => 'discriminator',
Items::class => 'items',
Property::class => ['properties', 'property'],
ExternalDocumentation::class => 'externalDocs',
Xml::class => 'xml',
AdditionalProperties::class => 'additionalProperties',
Examples::class => ['examples', 'example'],
Attachable::class => ['attachables'],
];
}
@@ -0,0 +1,108 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class AdditionalProperties extends \OpenApi\Annotations\AdditionalProperties
{
/**
* @param string|non-empty-array<string>|null $type
* @param string|class-string|object|null $ref
* @param string[] $required
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[]|bool[]|\UnitEnum[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
// schema
string|object|null $ref = null,
?string $schema = null,
?string $title = null,
?string $description = null,
?int $maxProperties = null,
?int $minProperties = null,
?array $required = null,
?array $properties = null,
string|array|null $type = null,
?string $format = null,
?Items $items = null,
?string $collectionFormat = null,
mixed $default = Generator::UNDEFINED,
$maximum = null,
bool|int|float|null $exclusiveMaximum = null,
$minimum = null,
bool|int|float|null $exclusiveMinimum = null,
?int $maxLength = null,
?int $minLength = null,
?int $maxItems = null,
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
?Xml $xml = null,
?ExternalDocumentation $externalDocs = null,
mixed $example = Generator::UNDEFINED,
?bool $nullable = null,
?bool $deprecated = null,
?array $allOf = null,
?array $anyOf = null,
?array $oneOf = null,
AdditionalProperties|bool|null $additionalProperties = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'ref' => $ref ?? Generator::UNDEFINED,
'schema' => $schema ?? Generator::UNDEFINED,
'title' => $title ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'maxProperties' => $maxProperties ?? Generator::UNDEFINED,
'minProperties' => $minProperties ?? Generator::UNDEFINED,
'required' => $required ?? Generator::UNDEFINED,
'properties' => $properties ?? Generator::UNDEFINED,
'type' => $type ?? Generator::UNDEFINED,
'format' => $format ?? Generator::UNDEFINED,
'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED,
'default' => $default,
'maximum' => $maximum ?? Generator::UNDEFINED,
'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED,
'minimum' => $minimum ?? Generator::UNDEFINED,
'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED,
'maxLength' => $maxLength ?? Generator::UNDEFINED,
'minLength' => $minLength ?? Generator::UNDEFINED,
'maxItems' => $maxItems ?? Generator::UNDEFINED,
'minItems' => $minItems ?? Generator::UNDEFINED,
'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED,
'pattern' => $pattern ?? Generator::UNDEFINED,
'enum' => $enum ?? Generator::UNDEFINED,
'readOnly' => $readOnly ?? Generator::UNDEFINED,
'writeOnly' => $writeOnly ?? Generator::UNDEFINED,
'xml' => $xml ?? Generator::UNDEFINED,
'example' => $example,
'nullable' => $nullable ?? Generator::UNDEFINED,
'deprecated' => $deprecated ?? Generator::UNDEFINED,
'allOf' => $allOf ?? Generator::UNDEFINED,
'anyOf' => $anyOf ?? Generator::UNDEFINED,
'oneOf' => $oneOf ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'attachables' => $attachables ?? Generator::UNDEFINED,
'value' => $this->combine($items, $discriminator, $externalDocs, $additionalProperties, $attachables),
]);
}
}
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_ALL | \Attribute::IS_REPEATABLE)]
class Attachable extends \OpenApi\Annotations\Attachable
{
public function __construct(array $properties = [])
{
parent::__construct($properties);
}
}
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Components extends \OpenApi\Annotations\Components
{
/**
* @param array<Schema|\OpenApi\Annotations\Schema>|null $schemas
* @param Response[]|null $responses
* @param Parameter[]|null $parameters
* @param RequestBody[]|null $requestBodies
* @param Examples[]|null $examples
* @param Header[]|null $headers
* @param SecurityScheme[]|null $securitySchemes
* @param Link[]|null $links
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?array $schemas = null,
?array $responses = null,
?array $parameters = null,
?array $requestBodies = null,
?array $examples = null,
?array $headers = null,
?array $securitySchemes = null,
?array $links = null,
?array $callbacks = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'callbacks' => $callbacks ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'attachables' => $attachables ?? Generator::UNDEFINED,
'value' => $this->combine($schemas, $responses, $parameters, $examples, $requestBodies, $headers, $securitySchemes, $links, $attachables),
]);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Contact extends \OpenApi\Annotations\Contact
{
/**
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $name = null,
?string $url = null,
?string $email = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'name' => $name ?? Generator::UNDEFINED,
'url' => $url ?? Generator::UNDEFINED,
'email' => $email ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($attachables),
]);
}
}
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)]
class CookieParameter extends Parameter
{
/**
* @inheritdoc
*/
public $in = 'cookie';
}
+13
View File
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Delete extends \OpenApi\Annotations\Delete
{
use OperationTrait;
}
@@ -0,0 +1,33 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Discriminator extends \OpenApi\Annotations\Discriminator
{
/**
* @param string[]|null $mapping
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $propertyName = null,
?array $mapping = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'propertyName' => $propertyName ?? Generator::UNDEFINED,
'mapping' => $mapping ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($attachables),
]);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class Examples extends \OpenApi\Annotations\Examples
{
/**
* @param string|class-string|object|null $ref
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $example = null,
?string $summary = null,
?string $description = null,
int|string|array|null $value = null,
?string $externalValue = null,
string|object|null $ref = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'example' => $example ?? Generator::UNDEFINED,
'summary' => $summary ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'value' => $value ?? Generator::UNDEFINED,
'externalValue' => $externalValue ?? Generator::UNDEFINED,
'ref' => $ref ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
]);
if ($attachables) {
$this->merge($attachables);
}
}
}
@@ -0,0 +1,32 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class ExternalDocumentation extends \OpenApi\Annotations\ExternalDocumentation
{
/**
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $description = null,
?string $url = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'description' => $description ?? Generator::UNDEFINED,
'url' => $url ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($attachables),
]);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Flow extends \OpenApi\Annotations\Flow
{
/**
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $authorizationUrl = null,
?string $tokenUrl = null,
?string $refreshUrl = null,
?string $flow = null,
?array $scopes = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'authorizationUrl' => $authorizationUrl ?? Generator::UNDEFINED,
'tokenUrl' => $tokenUrl ?? Generator::UNDEFINED,
'refreshUrl' => $refreshUrl ?? Generator::UNDEFINED,
'flow' => $flow ?? Generator::UNDEFINED,
'scopes' => $scopes ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($attachables),
]);
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Get extends \OpenApi\Annotations\Get
{
use OperationTrait;
}
+13
View File
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Head extends \OpenApi\Annotations\Head
{
use OperationTrait;
}
+41
View File
@@ -0,0 +1,41 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Header extends \OpenApi\Annotations\Header
{
/**
* @param string|class-string|object|null $ref
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
string|object|null $ref = null,
?string $header = null,
?string $description = null,
?bool $required = null,
?Schema $schema = null,
?bool $deprecated = null,
?bool $allowEmptyValue = null,
// annotation4
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'ref' => $ref ?? Generator::UNDEFINED,
'header' => $header ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'required' => $required ?? Generator::UNDEFINED,
'deprecated' => $deprecated ?? Generator::UNDEFINED,
'allowEmptyValue' => $allowEmptyValue ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($attachables, $schema),
]);
}
}
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)]
class HeaderParameter extends Parameter
{
/**
* @inheritdoc
*/
public $in = 'header';
}
+38
View File
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Info extends \OpenApi\Annotations\Info
{
/**
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $version = null,
?string $description = null,
?string $title = null,
?string $termsOfService = null,
?Contact $contact = null,
?License $license = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'version' => $version ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'title' => $title ?? Generator::UNDEFINED,
'termsOfService' => $termsOfService ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($contact, $license, $attachables),
]);
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class Items extends \OpenApi\Annotations\Items
{
/**
* @param string|non-empty-array<string>|null $type
* @param string|class-string|object|null $ref
* @param string[] $required
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[]|bool[]|\UnitEnum[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
// schema
string|object|null $ref = null,
?string $schema = null,
?string $title = null,
?string $description = null,
?int $maxProperties = null,
?int $minProperties = null,
?array $required = null,
?array $properties = null,
string|array|null $type = null,
?string $format = null,
?Items $items = null,
?string $collectionFormat = null,
mixed $default = Generator::UNDEFINED,
$maximum = null,
bool|int|float|null $exclusiveMaximum = null,
$minimum = null,
bool|int|float|null $exclusiveMinimum = null,
?int $maxLength = null,
?int $minLength = null,
?int $maxItems = null,
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
?Xml $xml = null,
?ExternalDocumentation $externalDocs = null,
mixed $example = Generator::UNDEFINED,
?bool $nullable = null,
?bool $deprecated = null,
?array $allOf = null,
?array $anyOf = null,
?array $oneOf = null,
AdditionalProperties|bool|null $additionalProperties = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
// schema
'ref' => $ref ?? Generator::UNDEFINED,
'schema' => $schema ?? Generator::UNDEFINED,
'title' => $title ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'maxProperties' => $maxProperties ?? Generator::UNDEFINED,
'minProperties' => $minProperties ?? Generator::UNDEFINED,
'required' => $required ?? Generator::UNDEFINED,
'properties' => $properties ?? Generator::UNDEFINED,
'type' => $type ?? Generator::UNDEFINED,
'format' => $format ?? Generator::UNDEFINED,
'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED,
'default' => $default,
'maximum' => $maximum ?? Generator::UNDEFINED,
'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED,
'minimum' => $minimum ?? Generator::UNDEFINED,
'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED,
'maxLength' => $maxLength ?? Generator::UNDEFINED,
'minLength' => $minLength ?? Generator::UNDEFINED,
'maxItems' => $maxItems ?? Generator::UNDEFINED,
'minItems' => $minItems ?? Generator::UNDEFINED,
'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED,
'pattern' => $pattern ?? Generator::UNDEFINED,
'enum' => $enum ?? Generator::UNDEFINED,
'readOnly' => $readOnly ?? Generator::UNDEFINED,
'writeOnly' => $writeOnly ?? Generator::UNDEFINED,
'xml' => $xml ?? Generator::UNDEFINED,
'example' => $example,
'nullable' => $nullable ?? Generator::UNDEFINED,
'deprecated' => $deprecated ?? Generator::UNDEFINED,
'allOf' => $allOf ?? Generator::UNDEFINED,
'anyOf' => $anyOf ?? Generator::UNDEFINED,
'oneOf' => $oneOf ?? Generator::UNDEFINED,
'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED,
// annotation
'x' => $x ?? Generator::UNDEFINED,
'attachables' => $attachables ?? Generator::UNDEFINED,
'value' => $this->combine($items, $discriminator, $externalDocs, $attachables),
]);
}
}
@@ -0,0 +1,114 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class JsonContent extends \OpenApi\Annotations\JsonContent
{
/**
* @param string|non-empty-array<string>|null $type
* @param string|class-string|object|null $ref
* @param array<string,Examples> $examples
* @param string[] $required
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[]|bool[]|\UnitEnum[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?array $examples = null,
// schema
string|object|null $ref = null,
?string $schema = null,
?string $title = null,
?string $description = null,
?int $maxProperties = null,
?int $minProperties = null,
?array $required = null,
?array $properties = null,
string|array|null $type = null,
?string $format = null,
?Items $items = null,
?string $collectionFormat = null,
mixed $default = Generator::UNDEFINED,
$maximum = null,
bool|int|float|null $exclusiveMaximum = null,
$minimum = null,
bool|int|float|null $exclusiveMinimum = null,
?int $maxLength = null,
?int $minLength = null,
?int $maxItems = null,
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
?Xml $xml = null,
?ExternalDocumentation $externalDocs = null,
mixed $example = Generator::UNDEFINED,
?bool $nullable = null,
?bool $deprecated = null,
?array $allOf = null,
?array $anyOf = null,
?array $oneOf = null,
AdditionalProperties|bool|null $additionalProperties = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'examples' => $examples ?? Generator::UNDEFINED,
// schema
'ref' => $ref ?? Generator::UNDEFINED,
'schema' => $schema ?? Generator::UNDEFINED,
'title' => $title ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'maxProperties' => $maxProperties ?? Generator::UNDEFINED,
'minProperties' => $minProperties ?? Generator::UNDEFINED,
'required' => $required ?? Generator::UNDEFINED,
'properties' => $properties ?? Generator::UNDEFINED,
'type' => $type ?? Generator::UNDEFINED,
'format' => $format ?? Generator::UNDEFINED,
'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED,
'default' => $default,
'maximum' => $maximum ?? Generator::UNDEFINED,
'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED,
'minimum' => $minimum ?? Generator::UNDEFINED,
'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED,
'maxLength' => $maxLength ?? Generator::UNDEFINED,
'minLength' => $minLength ?? Generator::UNDEFINED,
'maxItems' => $maxItems ?? Generator::UNDEFINED,
'minItems' => $minItems ?? Generator::UNDEFINED,
'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED,
'pattern' => $pattern ?? Generator::UNDEFINED,
'enum' => $enum ?? Generator::UNDEFINED,
'readOnly' => $readOnly ?? Generator::UNDEFINED,
'writeOnly' => $writeOnly ?? Generator::UNDEFINED,
'xml' => $xml ?? Generator::UNDEFINED,
'example' => $example,
'nullable' => $nullable ?? Generator::UNDEFINED,
'deprecated' => $deprecated ?? Generator::UNDEFINED,
'allOf' => $allOf ?? Generator::UNDEFINED,
'anyOf' => $anyOf ?? Generator::UNDEFINED,
'oneOf' => $oneOf ?? Generator::UNDEFINED,
'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED,
// annotation
'x' => $x ?? Generator::UNDEFINED,
'attachables' => $attachables ?? Generator::UNDEFINED,
'value' => $this->combine($items, $discriminator, $externalDocs, $attachables),
]);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class License extends \OpenApi\Annotations\License
{
/**
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $name = null,
?string $identifier = null,
?string $url = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'name' => $name ?? Generator::UNDEFINED,
'identifier' => $identifier ?? Generator::UNDEFINED,
'url' => $url ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($attachables),
]);
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class Link extends \OpenApi\Annotations\Link
{
/**
* @param string|class-string|object|null $ref
* @param array<string,mixed> $parameters
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $link = null,
?string $operationRef = null,
string|object|null $ref = null,
?string $operationId = null,
?array $parameters = null,
mixed $requestBody = null,
?string $description = null,
?Server $server = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'link' => $link ?? Generator::UNDEFINED,
'operationRef' => $operationRef ?? Generator::UNDEFINED,
'ref' => $ref ?? Generator::UNDEFINED,
'operationId' => $operationId ?? Generator::UNDEFINED,
'parameters' => $parameters ?? Generator::UNDEFINED,
'requestBody' => $requestBody ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($server, $attachables),
]);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class MediaType extends \OpenApi\Annotations\MediaType
{
/**
* @param array<string,Examples> $examples
* @param array<string,mixed> $encoding
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $mediaType = null,
?Schema $schema = null,
mixed $example = Generator::UNDEFINED,
?array $examples = null,
?array $encoding = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'mediaType' => $mediaType ?? Generator::UNDEFINED,
'example' => $example,
'encoding' => $encoding ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($schema, $examples, $attachables),
]);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class OpenApi extends \OpenApi\Annotations\OpenApi
{
/**
* @param Server[]|null $servers
* @param Tag[]|null $tags
* @param PathItem[]|null $paths
* @param Webhook[]|null $webhooks
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
string $openapi = self::DEFAULT_VERSION,
?Info $info = null,
?array $servers = null,
?array $security = null,
?array $tags = null,
?ExternalDocumentation $externalDocs = null,
?array $paths = null,
?Components $components = null,
?array $webhooks = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'openapi' => $openapi,
'security' => $security ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($info, $servers, $tags, $externalDocs, $paths, $components, $webhooks, $attachables),
]);
}
}
@@ -0,0 +1,54 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
trait OperationTrait
{
/**
* @param array $security
* @param Server[] $servers
* @param string[] $tags
* @param Parameter[] $parameters
* @param Response[] $responses
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $path = null,
?string $operationId = null,
?string $description = null,
?string $summary = null,
?array $security = null,
?array $servers = null,
?RequestBody $requestBody = null,
?array $tags = null,
?array $parameters = null,
?array $responses = null,
?array $callbacks = null,
?ExternalDocumentation $externalDocs = null,
?bool $deprecated = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'path' => $path ?? Generator::UNDEFINED,
'operationId' => $operationId ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'summary' => $summary ?? Generator::UNDEFINED,
'security' => $security ?? Generator::UNDEFINED,
'servers' => $servers ?? Generator::UNDEFINED,
'tags' => $tags ?? Generator::UNDEFINED,
'callbacks' => $callbacks ?? Generator::UNDEFINED,
'deprecated' => $deprecated ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($requestBody, $responses, $parameters, $externalDocs, $attachables),
]);
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Options extends \OpenApi\Annotations\Options
{
use OperationTrait;
}
+13
View File
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)]
class Parameter extends \OpenApi\Annotations\Parameter
{
use ParameterTrait;
}
@@ -0,0 +1,61 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
trait ParameterTrait
{
/**
* @param string|class-string|object|null $ref
* @param array<string,Examples> $examples
* @param array<MediaType>|JsonContent|XmlContent|Attachable|null $content
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $parameter = null,
?string $name = null,
?string $description = null,
?string $in = null,
?bool $required = null,
?bool $deprecated = null,
?bool $allowEmptyValue = null,
string|object|null $ref = null,
?Schema $schema = null,
mixed $example = Generator::UNDEFINED,
?array $examples = null,
array|JsonContent|XmlContent|Attachable|null $content = null,
?string $style = null,
?bool $explode = null,
?bool $allowReserved = null,
?array $spaceDelimited = null,
?array $pipeDelimited = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'parameter' => $parameter ?? Generator::UNDEFINED,
'name' => $name ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'in' => Generator::isDefault($this->in) ? $in : $this->in,
'required' => $required ?? Generator::UNDEFINED,
'deprecated' => $deprecated ?? Generator::UNDEFINED,
'allowEmptyValue' => $allowEmptyValue ?? Generator::UNDEFINED,
'ref' => $ref ?? Generator::UNDEFINED,
'example' => $example,
'style' => $style ?? Generator::UNDEFINED,
'explode' => $explode ?? Generator::UNDEFINED,
'allowReserved' => $allowReserved ?? Generator::UNDEFINED,
'spaceDelimited' => $spaceDelimited ?? Generator::UNDEFINED,
'pipeDelimited' => $pipeDelimited ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($schema, $examples, $content, $attachables),
]);
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Patch extends \OpenApi\Annotations\Patch
{
use OperationTrait;
}
+49
View File
@@ -0,0 +1,49 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class PathItem extends \OpenApi\Annotations\PathItem
{
/**
* @param string|class-string|object|null $ref
* @param Server[]|null $servers
* @param Parameter[]|null $parameters
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $path = null,
string|object|null $ref = null,
?string $summary = null,
?string $description = null,
?Get $get = null,
?Put $put = null,
?Post $post = null,
?Delete $delete = null,
?Options $options = null,
?Head $head = null,
?Patch $patch = null,
?Trace $trace = null,
?array $servers = null,
?array $parameters = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'path' => $path ?? Generator::UNDEFINED,
'ref' => $ref ?? Generator::UNDEFINED,
'summary' => $summary ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($get, $put, $post, $delete, $options, $head, $patch, $trace, $servers, $parameters, $attachables),
]);
}
}
@@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)]
class PathParameter extends Parameter
{
/**
* @inheritdoc
*/
public $in = 'path';
/**
* @inheritdoc
*/
public $required = true;
}
+13
View File
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Post extends \OpenApi\Annotations\Post
{
use OperationTrait;
}
+113
View File
@@ -0,0 +1,113 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER | \Attribute::TARGET_CLASS_CONSTANT | \Attribute::IS_REPEATABLE)]
class Property extends \OpenApi\Annotations\Property
{
/**
* @param string|non-empty-array<string>|null $type
* @param string|class-string|object|null $ref
* @param string[] $required
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[]|bool[]|\UnitEnum[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $property = null,
// schema
string|object|null $ref = null,
?string $schema = null,
?string $title = null,
?string $description = null,
?int $maxProperties = null,
?int $minProperties = null,
?array $required = null,
?array $properties = null,
string|array|null $type = null,
?string $format = null,
?Items $items = null,
?string $collectionFormat = null,
mixed $default = Generator::UNDEFINED,
$maximum = null,
bool|int|float|null $exclusiveMaximum = null,
$minimum = null,
bool|int|float|null $exclusiveMinimum = null,
?int $maxLength = null,
?int $minLength = null,
?int $maxItems = null,
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
?Xml $xml = null,
?ExternalDocumentation $externalDocs = null,
mixed $example = Generator::UNDEFINED,
?bool $nullable = null,
?bool $deprecated = null,
?array $allOf = null,
?array $anyOf = null,
?array $oneOf = null,
AdditionalProperties|bool|null $additionalProperties = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'property' => $property ?? Generator::UNDEFINED,
// schema
'ref' => $ref ?? Generator::UNDEFINED,
'schema' => $schema ?? Generator::UNDEFINED,
'title' => $title ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'maxProperties' => $maxProperties ?? Generator::UNDEFINED,
'minProperties' => $minProperties ?? Generator::UNDEFINED,
'required' => $required ?? Generator::UNDEFINED,
'properties' => $properties ?? Generator::UNDEFINED,
'type' => $type ?? Generator::UNDEFINED,
'format' => $format ?? Generator::UNDEFINED,
'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED,
'default' => $default,
'maximum' => $maximum ?? Generator::UNDEFINED,
'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED,
'minimum' => $minimum ?? Generator::UNDEFINED,
'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED,
'maxLength' => $maxLength ?? Generator::UNDEFINED,
'minLength' => $minLength ?? Generator::UNDEFINED,
'maxItems' => $maxItems ?? Generator::UNDEFINED,
'minItems' => $minItems ?? Generator::UNDEFINED,
'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED,
'pattern' => $pattern ?? Generator::UNDEFINED,
'enum' => $enum ?? Generator::UNDEFINED,
'readOnly' => $readOnly ?? Generator::UNDEFINED,
'writeOnly' => $writeOnly ?? Generator::UNDEFINED,
'xml' => $xml ?? Generator::UNDEFINED,
'example' => $example,
'nullable' => $nullable ?? Generator::UNDEFINED,
'deprecated' => $deprecated ?? Generator::UNDEFINED,
'allOf' => $allOf ?? Generator::UNDEFINED,
'anyOf' => $anyOf ?? Generator::UNDEFINED,
'oneOf' => $oneOf ?? Generator::UNDEFINED,
'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED,
// annotation
'x' => $x ?? Generator::UNDEFINED,
'attachables' => $attachables ?? Generator::UNDEFINED,
'value' => $this->combine($items, $discriminator, $externalDocs, $attachables),
]);
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Put extends \OpenApi\Annotations\Put
{
use OperationTrait;
}
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)]
class QueryParameter extends Parameter
{
/**
* @inheritdoc
*/
public $in = 'query';
}
@@ -0,0 +1,40 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
use OpenApi\Annotations as OA;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER | \Attribute::IS_REPEATABLE)]
class RequestBody extends OA\RequestBody
{
/**
* @param string|class-string|object|null $ref
* @param array<MediaType|JsonContent|XmlContent>|MediaType|JsonContent|XmlContent|Attachable|null $content
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
string|object|null $ref = null,
?string $request = null,
?string $description = null,
?bool $required = null,
array|MediaType|JsonContent|XmlContent|Attachable|null $content = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'ref' => $ref ?? Generator::UNDEFINED,
'request' => $request ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'required' => $required ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($content, $attachables),
]);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Annotations as OA;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Response extends OA\Response
{
/**
* @param string|class-string|object|null $ref
* @param Header[] $headers
* @param MediaType|JsonContent|XmlContent|Attachable|array<MediaType|JsonContent|XmlContent|Attachable> $content
* @param Link[] $links
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
string|object|null $ref = null,
int|string|null $response = null,
?string $description = null,
?array $headers = null,
MediaType|JsonContent|XmlContent|Attachable|array|null $content = null,
?array $links = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'ref' => $ref ?? Generator::UNDEFINED,
'response' => $response ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($headers, $content, $links, $attachables),
]);
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class Schema extends \OpenApi\Annotations\Schema
{
/**
* @param string|non-empty-array<string>|null $type
* @param string|class-string|object|null $ref
* @param string[] $required
* @param Property[] $properties
* @param int|float $maximum
* @param int|float $minimum
* @param string[]|int[]|float[]|bool[]|\UnitEnum[]|class-string $enum
* @param array<Schema|\OpenApi\Annotations\Schema> $allOf
* @param array<Schema|\OpenApi\Annotations\Schema> $anyOf
* @param array<Schema|\OpenApi\Annotations\Schema> $oneOf
* @param mixed $const
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
// schema
string|object|null $ref = null,
?string $schema = null,
?string $title = null,
?string $description = null,
?int $maxProperties = null,
?int $minProperties = null,
?array $required = null,
?array $properties = null,
string|array|null $type = null,
?string $format = null,
?Items $items = null,
?string $collectionFormat = null,
mixed $default = Generator::UNDEFINED,
$maximum = null,
bool|int|float|null $exclusiveMaximum = null,
$minimum = null,
bool|int|float|null $exclusiveMinimum = null,
?int $maxLength = null,
?int $minLength = null,
?int $maxItems = null,
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
?Xml $xml = null,
?ExternalDocumentation $externalDocs = null,
mixed $example = Generator::UNDEFINED,
?bool $nullable = null,
?bool $deprecated = null,
?array $allOf = null,
?array $anyOf = null,
?array $oneOf = null,
AdditionalProperties|bool|null $additionalProperties = null,
mixed $const = Generator::UNDEFINED,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'ref' => $ref ?? Generator::UNDEFINED,
'schema' => $schema ?? Generator::UNDEFINED,
'title' => $title ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'maxProperties' => $maxProperties ?? Generator::UNDEFINED,
'minProperties' => $minProperties ?? Generator::UNDEFINED,
'required' => $required ?? Generator::UNDEFINED,
'properties' => $properties ?? Generator::UNDEFINED,
'type' => $type ?? Generator::UNDEFINED,
'format' => $format ?? Generator::UNDEFINED,
'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED,
'default' => $default,
'maximum' => $maximum ?? Generator::UNDEFINED,
'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED,
'minimum' => $minimum ?? Generator::UNDEFINED,
'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED,
'maxLength' => $maxLength ?? Generator::UNDEFINED,
'minLength' => $minLength ?? Generator::UNDEFINED,
'maxItems' => $maxItems ?? Generator::UNDEFINED,
'minItems' => $minItems ?? Generator::UNDEFINED,
'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED,
'pattern' => $pattern ?? Generator::UNDEFINED,
'enum' => $enum ?? Generator::UNDEFINED,
'readOnly' => $readOnly ?? Generator::UNDEFINED,
'writeOnly' => $writeOnly ?? Generator::UNDEFINED,
'xml' => $xml ?? Generator::UNDEFINED,
'example' => $example,
'nullable' => $nullable ?? Generator::UNDEFINED,
'deprecated' => $deprecated ?? Generator::UNDEFINED,
'allOf' => $allOf ?? Generator::UNDEFINED,
'anyOf' => $anyOf ?? Generator::UNDEFINED,
'oneOf' => $oneOf ?? Generator::UNDEFINED,
'additionalProperties' => $additionalProperties ?? Generator::UNDEFINED,
'const' => $const,
'x' => $x ?? Generator::UNDEFINED,
'attachables' => $attachables ?? Generator::UNDEFINED,
'value' => $this->combine($items, $discriminator, $externalDocs, $attachables),
]);
}
}
@@ -0,0 +1,50 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
class SecurityScheme extends \OpenApi\Annotations\SecurityScheme
{
/**
* @param string|non-empty-array<string>|null $type
* @param string|class-string|object|null $ref
* @param Flow[] $flows
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
string|object|null $ref = null,
?string $securityScheme = null,
string|array|null $type = null,
?string $description = null,
?string $name = null,
?string $in = null,
?string $bearerFormat = null,
?string $scheme = null,
?string $openIdConnectUrl = null,
?array $flows = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'ref' => $ref ?? Generator::UNDEFINED,
'securityScheme' => $securityScheme ?? Generator::UNDEFINED,
'type' => $type ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'name' => $name ?? Generator::UNDEFINED,
'in' => $in ?? Generator::UNDEFINED,
'bearerFormat' => $bearerFormat ?? Generator::UNDEFINED,
'scheme' => $scheme ?? Generator::UNDEFINED,
'openIdConnectUrl' => $openIdConnectUrl ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($flows, $attachables),
]);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class Server extends \OpenApi\Annotations\Server
{
/**
* @param ServerVariable[] $variables
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $url = null,
?string $description = null,
?array $variables = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'url' => $url ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($variables, $attachables),
]);
}
}
@@ -0,0 +1,39 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS)]
class ServerVariable extends \OpenApi\Annotations\ServerVariable
{
/**
* @param string[]|int[]|float[]|bool[]|\UnitEnum[]|class-string|null $enum
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $serverVariable = null,
?string $description = null,
?string $default = null,
array|string|null $enum = null,
?array $variables = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'serverVariable' => $serverVariable ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'default' => $default ?? Generator::UNDEFINED,
'enum' => $enum ?? Generator::UNDEFINED,
'variables' => $variables ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($attachables),
]);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Tag extends \OpenApi\Annotations\Tag
{
/**
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $name = null,
?string $description = null,
?ExternalDocumentation $externalDocs = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'name' => $name ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($externalDocs, $attachables),
]);
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Trace extends \OpenApi\Annotations\Trace
{
use OperationTrait;
}
+51
View File
@@ -0,0 +1,51 @@
<?php declare(strict_types=1);
/**
* @license Apache 2.0
*/
namespace OpenApi\Attributes;
use OpenApi\Generator;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class Webhook extends \OpenApi\Annotations\Webhook
{
/**
* @param string|class-string|object|null $ref
* @param Server[]|null $servers
* @param Parameter[]|null $parameters
* @param array<string,mixed>|null $x
* @param Attachable[]|null $attachables
*/
public function __construct(
?string $webhook = null,
?string $path = null,
string|object|null $ref = null,
?string $summary = null,
?string $description = null,
?Get $get = null,
?Put $put = null,
?Post $post = null,
?Delete $delete = null,
?Options $options = null,
?Head $head = null,
?Patch $patch = null,
?Trace $trace = null,
?array $servers = null,
?array $parameters = null,
// annotation
?array $x = null,
?array $attachables = null
) {
parent::__construct([
'webhook' => $webhook ?? Generator::UNDEFINED,
'path' => $path ?? Generator::UNDEFINED,
'ref' => $ref ?? Generator::UNDEFINED,
'summary' => $summary ?? Generator::UNDEFINED,
'description' => $description ?? Generator::UNDEFINED,
'x' => $x ?? Generator::UNDEFINED,
'value' => $this->combine($get, $put, $post, $delete, $options, $head, $patch, $trace, $servers, $parameters, $attachables),
]);
}
}

Some files were not shown because too many files have changed in this diff Show More