├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── bin └── openapi ├── composer.json ├── package.json ├── rector.php └── src ├── Analysers ├── AnalyserInterface.php ├── AnnotationFactoryInterface.php ├── AttributeAnnotationFactory.php ├── ComposerAutoloaderScanner.php ├── DocBlockAnnotationFactory.php ├── DocBlockParser.php ├── ReflectionAnalyser.php └── TokenScanner.php ├── Analysis.php ├── Annotations ├── AbstractAnnotation.php ├── AdditionalProperties.php ├── Attachable.php ├── Components.php ├── Contact.php ├── CookieParameter.php ├── Delete.php ├── Discriminator.php ├── Examples.php ├── ExternalDocumentation.php ├── Flow.php ├── Get.php ├── Head.php ├── Header.php ├── HeaderParameter.php ├── Info.php ├── Items.php ├── JsonContent.php ├── License.php ├── Link.php ├── MediaType.php ├── OpenApi.php ├── Operation.php ├── Options.php ├── Parameter.php ├── Patch.php ├── PathItem.php ├── PathParameter.php ├── Post.php ├── Property.php ├── Put.php ├── QueryParameter.php ├── RequestBody.php ├── Response.php ├── Schema.php ├── SecurityScheme.php ├── Server.php ├── ServerVariable.php ├── Tag.php ├── Trace.php ├── Webhook.php ├── Xml.php └── XmlContent.php ├── Attributes ├── AdditionalProperties.php ├── Attachable.php ├── Components.php ├── Contact.php ├── CookieParameter.php ├── Delete.php ├── Discriminator.php ├── Examples.php ├── ExternalDocumentation.php ├── Flow.php ├── Get.php ├── Head.php ├── Header.php ├── HeaderParameter.php ├── Info.php ├── Items.php ├── JsonContent.php ├── License.php ├── Link.php ├── MediaType.php ├── OpenApi.php ├── OperationTrait.php ├── Options.php ├── Parameter.php ├── ParameterTrait.php ├── Patch.php ├── PathItem.php ├── PathParameter.php ├── Post.php ├── Property.php ├── Put.php ├── QueryParameter.php ├── RequestBody.php ├── Response.php ├── Schema.php ├── SecurityScheme.php ├── Server.php ├── ServerVariable.php ├── Tag.php ├── Trace.php ├── Webhook.php ├── Xml.php └── XmlContent.php ├── Context.php ├── Generator.php ├── GeneratorAwareInterface.php ├── GeneratorAwareTrait.php ├── Loggers ├── ConsoleLogger.php └── DefaultLogger.php ├── OpenApiException.php ├── Pipeline.php ├── Processors ├── AugmentDiscriminators.php ├── AugmentParameters.php ├── AugmentProperties.php ├── AugmentRefs.php ├── AugmentRequestBody.php ├── AugmentSchemas.php ├── AugmentTags.php ├── BuildPaths.php ├── CleanUnmerged.php ├── CleanUnusedComponents.php ├── Concerns │ ├── AnnotationTrait.php │ ├── DocblockTrait.php │ ├── MergePropertiesTrait.php │ ├── RefTrait.php │ └── TypesTrait.php ├── DocBlockDescriptions.php ├── ExpandClasses.php ├── ExpandEnums.php ├── ExpandInterfaces.php ├── ExpandTraits.php ├── MergeIntoComponents.php ├── MergeIntoOpenApi.php ├── MergeJsonContent.php ├── MergeXmlContent.php ├── OperationId.php └── PathFilter.php ├── Serializer.php └── Util.php /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Contributions of any kind are welcome. 4 | 5 | Feel free to submit [Github Issues](https://github.com/zircote/swagger-php/issues) 6 | or [pull requests](https://github.com/zircote/swagger-php/pulls). 7 | 8 | 9 | ## Quick Guide 10 | 11 | The documentation site has [some details](https://zircote.github.io/swagger-php/guide/under-the-hood.html#documentation) about internals. 12 | 13 | ### How-To 14 | 15 | * [Fork](https://help.github.com/articles/fork-a-repo/) the repo. 16 | * [Checkout](https://git-scm.com/docs/git-checkout) the branch you want to make changes on. 17 | * 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. 18 | * Install dependencies: `composer install`. 19 | * Create a new branch, e.g. `feature-foo` or `bugfix-bar`. 20 | * Make changes. 21 | * If you are adding functionality or fixing a bug - add a test! 22 | 23 | Prefer adding new test cases over modifying existing ones. 24 | * Update documentation: `composer docs:gen`. 25 | * Run static analysis using PHPStan/Psalm: `composer analyse`. 26 | * Check if tests pass: `composer test`. 27 | * Fix code style issues: `composer cs`. 28 | 29 | 30 | ## Documentation 31 | 32 | The documentation website is build from the [docs](docs/) folder with [vitepress](https://vitepress.vuejs.org). 33 | This process involves converting the existing markdown (`.md`) files into static HTML pages and publishing them. 34 | 35 | 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`. 36 | 37 | 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). 38 | 39 | 40 | ## Useful commands 41 | 42 | ### To run both unit tests and linting execute 43 | ```shell 44 | composer test 45 | ``` 46 | 47 | ### To run static-analysis execute 48 | ```shell 49 | composer analyse 50 | ``` 51 | 52 | ### Running unit tests only 53 | ```shell 54 | ./bin/phpunit 55 | ``` 56 | 57 | ### Regenerate reference markup docs 58 | ```shell 59 | composer docs:gen 60 | ``` 61 | 62 | ### Running linting only 63 | ```shell 64 | composer lint 65 | ``` 66 | 67 | ### To make `php-cs-fixer` fix linting errors 68 | ```shell 69 | composer cs 70 | ``` 71 | 72 | ### Run dev server for local development of `gh-pages` 73 | ```shell 74 | composer docs:dev 75 | ``` 76 | 77 | 78 | ## Project's Standards 79 | 80 | * [PSR-1: Basic Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) 81 | * [PSR-2: Coding Style Guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) 82 | * [PSR-4: Autoloading Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md) 83 | * [PSR-5: PHPDoc (draft)](https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md) 84 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | swagger-php 2 | Copyright 2022 The swagger-php project 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![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) 2 | [![Total Downloads](https://img.shields.io/packagist/dt/zircote/swagger-php.svg)](https://packagist.org/packages/zircote/swagger-php) 3 | [![License](https://img.shields.io/badge/license-Apache2.0-blue.svg)](LICENSE) 4 | 5 | # swagger-php 6 | 7 | Generate interactive [OpenAPI](https://www.openapis.org) documentation for your RESTful API using [PHP attributes](https://www.php.net/manual/en/language.attributes.overview.php) (preferred) or 8 | [doctrine annotations](https://www.doctrine-project.org/projects/annotations.html) (requires additional `doctrine/annotations` library). 9 | 10 | See the [documentation website](https://zircote.github.io/swagger-php/guide/attributes.html) for supported attributes and annotations. 11 | 12 | Annotations are deprecated and may be removed in a future release of swagger-php. 13 | 14 | ## Features 15 | 16 | - Compatible with the OpenAPI **3.0** and **3.1** specification. 17 | - Extracts information from code & existing phpdoc annotations. 18 | - Command-line interface available. 19 | - [Documentation site](https://zircote.github.io/swagger-php/) with a getting started guide. 20 | - Exceptional error reporting (with hints, context) 21 | - As of PHP 8.1 all annotations are also available as PHP attributes 22 | 23 | ## OpenAPI version support 24 | 25 | `swagger-php` allows to generate specs either for **OpenAPI 3.0.0** or **OpenAPI 3.1.0**. 26 | By default the spec will be in version `3.0.0`. The command line option `--version` may be used to change this 27 | to `3.1.0`. 28 | 29 | Programmatically, the method `Generator::setVersion()` can be used to change the version. 30 | 31 | ## Requirements 32 | 33 | `swagger-php` requires at least PHP 7.4 for annotations and PHP 8.1 for using attributes. 34 | 35 | ## Installation (with [Composer](https://getcomposer.org)) 36 | 37 | ```shell 38 | composer require zircote/swagger-php 39 | ``` 40 | 41 | 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. 42 | 43 | ```shell 44 | composer global require zircote/swagger-php 45 | ``` 46 | 47 | ### doctrine/annotations 48 | 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**. 49 | 50 | To use PHPDoc annotations this needs to be installed on top of `swagger-php`: 51 | ```shell 52 | composer require doctrine/annotations 53 | ``` 54 | 55 | If your code uses PHPDoc annotations you will need to install this as well: 56 | 57 | ```shell 58 | composer require doctrine/annotations 59 | ``` 60 | 61 | 62 | ## Usage 63 | 64 | Add annotations to your php files. 65 | 66 | ```php 67 | /** 68 | * @OA\Info(title="My First API", version="0.1") 69 | */ 70 | 71 | /** 72 | * @OA\Get( 73 | * path="/api/resource.json", 74 | * @OA\Response(response="200", description="An example resource") 75 | * ) 76 | */ 77 | ``` 78 | 79 | 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](docs/examples) for more examples. 80 | 81 | ### Usage from php 82 | 83 | Generate always-up-to-date documentation. 84 | 85 | ```php 86 | toYaml(); 91 | ``` 92 | Documentation of how to use the `Generator` class can be found in the [Generator reference](https://zircote.github.io/swagger-php/reference/generator). 93 | 94 | ### Usage from the Command Line Interface 95 | 96 | The `openapi` command line interface can be used to generate the documentation to a static yaml/json file. 97 | 98 | ```shell 99 | ./vendor/bin/openapi --help 100 | ``` 101 | 102 | ### Usage from the Deserializer 103 | 104 | Generate the OpenApi annotation object from a json string, which makes it easier to manipulate objects programmatically. 105 | 106 | ```php 107 | deserialize($jsonString, 'OpenApi\Annotations\OpenApi'); 113 | echo $openapi->toJson(); 114 | ``` 115 | 116 | ## [Contributing](CONTRIBUTING.md) 117 | 118 | ## More on OpenApi & Swagger 119 | 120 | - https://swagger.io 121 | - https://www.openapis.org 122 | - [OpenApi Documentation](https://swagger.io/docs/) 123 | - [OpenApi Specification](http://swagger.io/specification/) 124 | - [Related projects](docs/related-projects.md) 125 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zircote/swagger-php", 3 | "type": "library", 4 | "license": "Apache-2.0", 5 | "bin": [ 6 | "bin/openapi" 7 | ], 8 | "description": "Generate interactive documentation for your RESTful API using PHP attributes (preferred) or PHPDoc annotations", 9 | "keywords": [ 10 | "json", 11 | "rest", 12 | "api", 13 | "service discovery" 14 | ], 15 | "homepage": "https://github.com/zircote/swagger-php", 16 | "authors": [ 17 | { 18 | "name": "Robert Allen", 19 | "email": "zircote@gmail.com" 20 | }, 21 | { 22 | "name": "Bob Fanger", 23 | "email": "bfanger@gmail.com", 24 | "homepage": "https://bfanger.nl" 25 | }, 26 | { 27 | "name": "Martin Rademacher", 28 | "email": "mano@radebatz.net", 29 | "homepage": "https://radebatz.net" 30 | } 31 | ], 32 | "config": { 33 | "bin-dir": "bin", 34 | "optimize-autoloader": true, 35 | "sort-packages": true, 36 | "allow-plugins": { 37 | "composer/package-versions-deprecated": true 38 | } 39 | }, 40 | "minimum-stability": "stable", 41 | "extra": { 42 | "branch-alias": { 43 | "dev-master": "5.x-dev" 44 | } 45 | }, 46 | "require": { 47 | "php": ">=7.4", 48 | "ext-json": "*", 49 | "nikic/php-parser": "^4.19 || ^5.0", 50 | "psr/log": "^1.1 || ^2.0 || ^3.0", 51 | "symfony/deprecation-contracts": "^2 || ^3", 52 | "symfony/finder": "^5.0 || ^6.0 || ^7.0", 53 | "symfony/yaml": "^5.0 || ^6.0 || ^7.0" 54 | }, 55 | "autoload": { 56 | "psr-4": { 57 | "OpenApi\\": "src" 58 | } 59 | }, 60 | "autoload-dev": { 61 | "exclude-from-classmap": [ 62 | "/tests/Fixtures" 63 | ], 64 | "psr-4": { 65 | "OpenApi\\Tools\\": "tools/src/", 66 | "OpenApi\\Tests\\": "tests/", 67 | "AnotherNamespace\\": "tests/Fixtures/AnotherNamespace" 68 | } 69 | }, 70 | "require-dev": { 71 | "composer/package-versions-deprecated": "^1.11", 72 | "doctrine/annotations": "^2.0", 73 | "friendsofphp/php-cs-fixer": "^3.62.0", 74 | "phpstan/phpstan": "^1.6 || ^2.0", 75 | "phpunit/phpunit": "^9.0", 76 | "rector/rector": "^1.0 || ^2.0", 77 | "vimeo/psalm": "^4.30 || ^5.0" 78 | }, 79 | "conflict": { 80 | "symfony/process": ">=6, <6.4.14" 81 | }, 82 | "suggest": { 83 | "doctrine/annotations": "^2.0" 84 | }, 85 | "scripts-descriptions": { 86 | "cs": "Fix all codestyle issues", 87 | "rector": "Automatic refactoring", 88 | "lint": "Test codestyle", 89 | "test": "Run all PHP, codestyle and rector tests", 90 | "analyse": "Run static analysis (phpstan/psalm)", 91 | "spectral-examples": "Run spectral lint over all .yaml files in the docs/examples folder", 92 | "spectral-scratch": "Run spectral lint over all .yaml files in the tests/Fixtures/Scratch folder", 93 | "spectral": "Run all spectral tests", 94 | "redocly-examples": "Run redocly lint over all .yaml files in the docs/examples folder", 95 | "redocly-scratch": "Run redocly lint over all .yaml files in the tests/Fixtures/Scratch folder", 96 | "redocly": "Run all redocly tests", 97 | "docs:gen": "Rebuild reference documentation", 98 | "docs:dev": "Run dev server for local development of gh-pages", 99 | "docs:build": "Re-build static gh-pages" 100 | }, 101 | "scripts": { 102 | "cs": "export XDEBUG_MODE=off && php-cs-fixer fix --allow-risky=yes", 103 | "rector": "rector process src", 104 | "lint": [ 105 | "@cs --dry-run", 106 | "@rector --dry-run" 107 | ], 108 | "test": [ 109 | "export XDEBUG_MODE=off && phpunit", 110 | "@lint" 111 | ], 112 | "analyse": [ 113 | "export XDEBUG_MODE=off && phpstan analyse --memory-limit=2G", 114 | "export XDEBUG_MODE=off && psalm --threads=1" 115 | ], 116 | "spectral-examples": "for ff in `find docs/examples -name '*.yaml'`; do npm run spectral lint $ff; done", 117 | "spectral-scratch": "for ff in `find tests/Fixtures/Scratch -name '*.yaml'`; do npm run spectral lint $ff; done", 118 | "spectral": [ 119 | "@spectral-examples", 120 | "@spectral-scratch" 121 | ], 122 | "redocly-examples": "for ff in `find docs/examples -name '*.yaml'`; do npm run redocly lint $ff; done", 123 | "redocly-scratch": "for ff in `find tests/Fixtures/Scratch -name '*.yaml'`; do npm run redocly lint $ff; done", 124 | "redocly": [ 125 | "@redocly-examples", 126 | "@redocly-scratch" 127 | ], 128 | "docs:gen": [ 129 | "@php tools/refgen.php", 130 | "@php tools/procgen.php", 131 | "@php tools/examplegen.php" 132 | ], 133 | "docs:dev": "cd docs && npm run dev", 134 | "docs:build": [ 135 | "@docs:gen", 136 | "cd docs && npm run build" 137 | ] 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger-php-tools", 3 | "version": "1.0.0", 4 | "license": "Apache-2.0", 5 | "scripts": { 6 | "spectral": "spectral", 7 | "redocly": "redocly" 8 | }, 9 | "devDependencies": { 10 | "@stoplight/spectral-cli": "^6", 11 | "@redocly/cli": "^1.30" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withRules([ 16 | TypedPropertyFromStrictConstructorRector::class 17 | ]) 18 | ->withSkip([ 19 | CombineIfRector::class, 20 | ExplicitBoolCompareRector::class, 21 | ForRepeatedCountToOwnVariableRector::class, 22 | RemoveAlwaysTrueIfConditionRector::class => [ 23 | __DIR__ . '/src/Processors/ExpandEnums.php', 24 | ] , 25 | RemoveDeadInstanceOfRector::class => [ 26 | __DIR__ . '/src/Processors/ExpandEnums.php', 27 | ], 28 | ShortenElseIfRector::class, 29 | ]) 30 | ->withPreparedSets(true, true) 31 | ->withPhpVersion(PhpVersion::PHP_74); 32 | -------------------------------------------------------------------------------- /src/Analysers/AnalyserInterface.php: -------------------------------------------------------------------------------- 1 | top level annotations 22 | */ 23 | public function build(\Reflector $reflector, Context $context): array; 24 | } 25 | -------------------------------------------------------------------------------- /src/Analysers/ComposerAutoloaderScanner.php: -------------------------------------------------------------------------------- 1 | composer --optimized run in order to utilize 15 | * the generated class map. 16 | */ 17 | class ComposerAutoloaderScanner 18 | { 19 | /** 20 | * Collect all classes/interfaces/traits known by composer. 21 | * 22 | * @param array $namespaces 23 | * 24 | * @return array 25 | */ 26 | public function scan(array $namespaces): array 27 | { 28 | $units = []; 29 | if ($autoloader = $this->getComposerAutoloader()) { 30 | foreach (array_keys($autoloader->getClassMap()) as $unit) { 31 | foreach ($namespaces as $namespace) { 32 | if (0 === strpos($unit, $namespace)) { 33 | $units[] = $unit; 34 | break; 35 | } 36 | } 37 | } 38 | } 39 | 40 | return $units; 41 | } 42 | 43 | public static function getComposerAutoloader(): ?ClassLoader 44 | { 45 | foreach (spl_autoload_functions() as $fkt) { 46 | if (is_array($fkt) && $fkt[0] instanceof ClassLoader) { 47 | return $fkt[0]; 48 | } 49 | } 50 | 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Analysers/DocBlockAnnotationFactory.php: -------------------------------------------------------------------------------- 1 | docBlockParser = $docBlockParser ?: new DocBlockParser(); 23 | } 24 | 25 | public function isSupported(): bool 26 | { 27 | return DocBlockParser::isEnabled(); 28 | } 29 | 30 | public function setGenerator(Generator $generator) 31 | { 32 | $this->generator = $generator; 33 | 34 | $this->docBlockParser->setAliases($generator->getAliases()); 35 | 36 | return $this; 37 | } 38 | 39 | public function build(\Reflector $reflector, Context $context): array 40 | { 41 | $aliases = $this->generator ? $this->generator->getAliases() : []; 42 | 43 | if (method_exists($reflector, 'getShortName') && method_exists($reflector, 'getName')) { 44 | $aliases[strtolower($reflector->getShortName())] = $reflector->getName(); 45 | } 46 | 47 | if ($context->with('scanned')) { 48 | $details = $context->scanned; 49 | foreach ($details['uses'] as $alias => $name) { 50 | $aliasKey = strtolower($alias); 51 | if ($name != $alias && !array_key_exists($aliasKey, $aliases)) { 52 | // real aliases only 53 | $aliases[strtolower($alias)] = $name; 54 | } 55 | } 56 | } 57 | $this->docBlockParser->setAliases($aliases); 58 | 59 | if (method_exists($reflector, 'getDocComment') && ($comment = $reflector->getDocComment())) { 60 | $annotations = []; 61 | foreach ($this->docBlockParser->fromComment($comment, $context) as $instance) { 62 | if ($instance instanceof OA\AbstractAnnotation) { 63 | $annotations[] = $instance; 64 | } else { 65 | if ($context->is('other') === false) { 66 | $context->other = []; 67 | } 68 | $context->other[] = $instance; 69 | } 70 | } 71 | 72 | return $annotations; 73 | } 74 | 75 | return []; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Analysers/DocBlockParser.php: -------------------------------------------------------------------------------- 1 | $aliases 23 | */ 24 | public function __construct(array $aliases = []) 25 | { 26 | if (DocBlockParser::isEnabled()) { 27 | $docParser = new DocParser(); 28 | $docParser->setIgnoreNotImportedAnnotations(true); 29 | $docParser->setImports($aliases); 30 | $this->docParser = $docParser; 31 | } 32 | } 33 | 34 | /** 35 | * Check if we can process annotations. 36 | */ 37 | public static function isEnabled(): bool 38 | { 39 | return class_exists('Doctrine\\Common\\Annotations\\DocParser'); 40 | } 41 | 42 | /** 43 | * @param array $aliases 44 | */ 45 | public function setAliases(array $aliases): void 46 | { 47 | $this->docParser->setImports($aliases); 48 | } 49 | 50 | /** 51 | * Use doctrine to parse the comment block and return the detected annotations. 52 | * 53 | * @param string $comment a T_DOC_COMMENT 54 | * 55 | * @return array 56 | */ 57 | public function fromComment(string $comment, Context $context): array 58 | { 59 | $context->comment = $comment; 60 | 61 | try { 62 | Generator::$context = $context; 63 | if ($context->is('annotations') === false) { 64 | $context->annotations = []; 65 | } 66 | 67 | return $this->docParser->parse($comment, $context->getDebugLocation()); 68 | } catch (\Exception $e) { 69 | if (preg_match('/^(.+) at position ([0-9]+) in ' . preg_quote((string) $context, '/') . '\.$/', $e->getMessage(), $matches)) { 70 | $errorMessage = $matches[1]; 71 | $errorPos = (int) $matches[2]; 72 | $atPos = strpos($comment, '@'); 73 | $context->line -= substr_count($comment, "\n", $atPos + $errorPos) + 1; 74 | $lines = explode("\n", substr($comment, $atPos, $errorPos)); 75 | $context->character = strlen(array_pop($lines)) + 1; // position starts at 0 character starts at 1 76 | $context->logger->error($errorMessage . ' in ' . $context, ['exception' => $e]); 77 | } else { 78 | $context->logger->error( 79 | $e->getMessage() . ($context->filename ? ('; file=' . $context->filename) : ''), 80 | ['exception' => $e] 81 | ); 82 | } 83 | 84 | return []; 85 | } finally { 86 | Generator::$context = null; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Analysers/TokenScanner.php: -------------------------------------------------------------------------------- 1 | createForNewestSupportedVersion(); 32 | try { 33 | $stmts = $parser->parse(file_get_contents($filename)); 34 | } catch (Error $e) { 35 | throw new \RuntimeException($e->getMessage(), $e->getCode(), $e); 36 | } 37 | 38 | $result = []; 39 | $result += $this->collect_stmts($stmts, ''); 40 | foreach ($stmts as $stmt) { 41 | if ($stmt instanceof Namespace_) { 42 | $namespace = (string) $stmt->name; 43 | 44 | $result += $this->collect_stmts($stmt->stmts, $namespace); 45 | } 46 | } 47 | 48 | return $result; 49 | } 50 | 51 | protected function collect_stmts(array $stmts, string $namespace): array 52 | { 53 | /** @var array $uses */ 54 | $uses = []; 55 | $resolve = function (string $name) use ($namespace, &$uses) { 56 | if (array_key_exists($name, $uses)) { 57 | return $uses[$name]; 58 | } 59 | 60 | return $namespace . '\\' . $name; 61 | }; 62 | $details = function () use (&$uses) { 63 | return [ 64 | 'uses' => $uses, 65 | 'interfaces' => [], 66 | 'traits' => [], 67 | 'enums' => [], 68 | 'methods' => [], 69 | 'properties' => [], 70 | ]; 71 | }; 72 | $result = []; 73 | foreach ($stmts as $stmt) { 74 | switch (get_class($stmt)) { 75 | case Use_::class: 76 | $uses += $this->collect_uses($stmt); 77 | break; 78 | case Class_::class: 79 | $result += $this->collect_class($stmt, $details(), $resolve); 80 | break; 81 | case Interface_::class: 82 | $result += $this->collect_interface($stmt, $details(), $resolve); 83 | break; 84 | case Trait_::class: 85 | case Enum_::class: 86 | $result += $this->collect_classlike($stmt, $details(), $resolve); 87 | break; 88 | } 89 | } 90 | 91 | return $result; 92 | } 93 | 94 | protected function collect_uses(Use_ $stmt): array 95 | { 96 | $uses = []; 97 | 98 | foreach ($stmt->uses as $use) { 99 | $uses[(string) $use->getAlias()] = (string) $use->name; 100 | } 101 | 102 | return $uses; 103 | } 104 | 105 | protected function collect_classlike(ClassLike $stmt, array $details, callable $resolve): array 106 | { 107 | foreach ($stmt->getProperties() as $properties) { 108 | foreach ($properties->props as $prop) { 109 | $details['properties'][] = (string) $prop->name; 110 | } 111 | } 112 | 113 | foreach ($stmt->getMethods() as $method) { 114 | $details['methods'][] = (string) $method->name; 115 | } 116 | 117 | foreach ($stmt->getTraitUses() as $traitUse) { 118 | foreach ($traitUse->traits as $trait) { 119 | $details['traits'][] = $resolve((string) $trait); 120 | } 121 | } 122 | 123 | return [ 124 | $resolve($stmt->name->name) => $details, 125 | ]; 126 | } 127 | 128 | protected function collect_class(Class_ $stmt, array $details, callable $resolve): array 129 | { 130 | foreach ($stmt->implements as $implement) { 131 | $details['interfaces'][] = $resolve((string) $implement); 132 | } 133 | 134 | // promoted properties 135 | if ($ctor = $stmt->getMethod('__construct')) { 136 | foreach ($ctor->getParams() as $param) { 137 | if ($param->flags) { 138 | $details['properties'][] = $param->var->name; 139 | } 140 | } 141 | } 142 | 143 | return $this->collect_classlike($stmt, $details, $resolve); 144 | } 145 | 146 | protected function collect_interface(Interface_ $stmt, array $details, callable $resolve): array 147 | { 148 | foreach ($stmt->extends as $extend) { 149 | $details['interfaces'][] = $resolve((string) $extend); 150 | } 151 | 152 | return $this->collect_classlike($stmt, $details, $resolve); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Annotations/AdditionalProperties.php: -------------------------------------------------------------------------------- 1 | 'discriminator', 31 | Items::class => 'items', 32 | Property::class => ['properties', 'property'], 33 | ExternalDocumentation::class => 'externalDocs', 34 | Xml::class => 'xml', 35 | AdditionalProperties::class => 'additionalProperties', 36 | Attachable::class => ['attachables'], 37 | ]; 38 | } 39 | -------------------------------------------------------------------------------- /src/Annotations/Attachable.php: -------------------------------------------------------------------------------- 1 | |null List of valid parent annotation classes. If null, the default nesting rules apply. 69 | */ 70 | public function allowedParents(): ?array 71 | { 72 | return null; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Annotations/Components.php: -------------------------------------------------------------------------------- 1 | 37 | */ 38 | public $schemas = Generator::UNDEFINED; 39 | 40 | /** 41 | * Reusable Responses. 42 | * 43 | * @var Response[] 44 | */ 45 | public $responses = Generator::UNDEFINED; 46 | 47 | /** 48 | * Reusable Parameters. 49 | * 50 | * @var Parameter[] 51 | */ 52 | public $parameters = Generator::UNDEFINED; 53 | 54 | /** 55 | * Reusable Examples. 56 | * 57 | * @var array 58 | */ 59 | public $examples = Generator::UNDEFINED; 60 | 61 | /** 62 | * Reusable Request Bodies. 63 | * 64 | * @var RequestBody[] 65 | */ 66 | public $requestBodies = Generator::UNDEFINED; 67 | 68 | /** 69 | * Reusable Headers. 70 | * 71 | * @var Header[] 72 | */ 73 | public $headers = Generator::UNDEFINED; 74 | 75 | /** 76 | * Reusable Security Schemes. 77 | * 78 | * @var SecurityScheme[] 79 | */ 80 | public $securitySchemes = Generator::UNDEFINED; 81 | 82 | /** 83 | * Reusable Links. 84 | * 85 | * @var Link[] 86 | */ 87 | public $links = Generator::UNDEFINED; 88 | 89 | /** 90 | * Reusable Callbacks. 91 | * 92 | * @var array 93 | */ 94 | public $callbacks = Generator::UNDEFINED; 95 | 96 | /** 97 | * @inheritdoc 98 | */ 99 | public static $_parents = [ 100 | OpenApi::class, 101 | ]; 102 | 103 | /** 104 | * @inheritdoc 105 | */ 106 | public static $_nested = [ 107 | Response::class => ['responses', 'response'], 108 | Parameter::class => ['parameters', 'parameter'], 109 | PathParameter::class => ['parameters', 'parameter'], 110 | RequestBody::class => ['requestBodies', 'request'], 111 | Examples::class => ['examples', 'example'], 112 | Header::class => ['headers', 'header'], 113 | SecurityScheme::class => ['securitySchemes', 'securityScheme'], 114 | Link::class => ['links', 'link'], 115 | Schema::class => ['schemas', 'schema'], 116 | Attachable::class => ['attachables'], 117 | ]; 118 | 119 | /** 120 | * Returns a list of component annotation types. 121 | * 122 | * Each may be used as a root to resolve component refs 123 | */ 124 | public static function componentTypes(): array 125 | { 126 | return array_filter(array_keys(self::$_nested), fn ($value) => $value !== Attachable::class); 127 | } 128 | 129 | /** 130 | * Generate a #/components/... reference for the given annotation. 131 | * 132 | * A string component value always assumes type Schema. 133 | * 134 | * @param AbstractAnnotation|string $component 135 | */ 136 | public static function ref($component, bool $encode = true): string 137 | { 138 | if ($component instanceof AbstractAnnotation) { 139 | foreach (Components::$_nested as $type => $nested) { 140 | // exclude attachables 141 | if (2 == count($nested)) { 142 | if ($component instanceof $type) { 143 | $type = $nested[0]; 144 | $name = $component->{$nested[1]}; 145 | break; 146 | } 147 | } 148 | } 149 | } else { 150 | $type = 'schemas'; 151 | $name = $component; 152 | } 153 | 154 | return self::COMPONENTS_PREFIX . $type . '/' . ($encode ? Util::refEncode((string) $name) : $name); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Annotations/Contact.php: -------------------------------------------------------------------------------- 1 | 'string', 46 | 'url' => 'string', 47 | 'email' => 'string', 48 | ]; 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | public static $_parents = [ 54 | Info::class, 55 | ]; 56 | 57 | /** 58 | * @inheritdoc 59 | */ 60 | public static $_nested = [ 61 | Attachable::class => ['attachables'], 62 | ]; 63 | } 64 | -------------------------------------------------------------------------------- /src/Annotations/CookieParameter.php: -------------------------------------------------------------------------------- 1 | @OA\Request cookie parameter. 11 | * 12 | * @Annotation 13 | */ 14 | class CookieParameter extends Parameter 15 | { 16 | /** 17 | * @inheritdoc 18 | * This takes 'cookie' as the default location. 19 | */ 20 | public $in = 'cookie'; 21 | } 22 | -------------------------------------------------------------------------------- /src/Annotations/Delete.php: -------------------------------------------------------------------------------- 1 | 'string', 49 | ]; 50 | 51 | /** 52 | * @inheritdoc 53 | */ 54 | public static $_parents = [ 55 | Schema::class, 56 | Property::class, 57 | AdditionalProperties::class, 58 | Items::class, 59 | JsonContent::class, 60 | XmlContent::class, 61 | ]; 62 | 63 | /** 64 | * @inheritdoc 65 | */ 66 | public static $_nested = [ 67 | Attachable::class => ['attachables'], 68 | ]; 69 | } 70 | -------------------------------------------------------------------------------- /src/Annotations/Examples.php: -------------------------------------------------------------------------------- 1 | #/components/examples. 27 | * 28 | * @var string 29 | */ 30 | public $example = Generator::UNDEFINED; 31 | 32 | /** 33 | * Short description for the example. 34 | * 35 | * @var string 36 | */ 37 | public $summary = Generator::UNDEFINED; 38 | 39 | /** 40 | * Embedded literal example. 41 | * 42 | * The value field and externalValue field are mutually exclusive. 43 | * 44 | * To represent examples of media types that cannot naturally be represented 45 | * in JSON or YAML, use a string value to contain the example, escaping where necessary. 46 | * 47 | * @var string 48 | */ 49 | public $description = Generator::UNDEFINED; 50 | 51 | /** 52 | * Embedded literal example. 53 | * 54 | * The value field and externalValue field are mutually exclusive. 55 | * 56 | * To represent examples of media types that cannot naturally be represented 57 | * in JSON or YAML, use a string value to contain the example, escaping where necessary. 58 | * 59 | * @var int|string|array 60 | */ 61 | public $value = Generator::UNDEFINED; 62 | 63 | /** 64 | * An URL that points to the literal example. 65 | * 66 | * This provides the capability to reference examples that cannot easily be included 67 | * in JSON or YAML documents. 68 | * 69 | * The value field and externalValue field are mutually exclusive. 70 | * 71 | * @var string 72 | */ 73 | public $externalValue = Generator::UNDEFINED; 74 | 75 | public static $_types = [ 76 | 'summary' => 'string', 77 | 'description' => 'string', 78 | 'externalValue' => 'string', 79 | ]; 80 | 81 | public static $_required = ['summary']; 82 | 83 | public static $_parents = [ 84 | Components::class, 85 | Schema::class, 86 | Parameter::class, 87 | PathParameter::class, 88 | MediaType::class, 89 | JsonContent::class, 90 | XmlContent::class, 91 | ]; 92 | 93 | /** 94 | * @inheritdoc 95 | */ 96 | public static $_nested = [ 97 | Attachable::class => ['attachables'], 98 | ]; 99 | } 100 | -------------------------------------------------------------------------------- /src/Annotations/ExternalDocumentation.php: -------------------------------------------------------------------------------- 1 | 'string', 39 | 'url' => 'string', 40 | ]; 41 | 42 | /** 43 | * @inheritdoc 44 | */ 45 | public static $_required = ['url']; 46 | 47 | /** 48 | * @inheritdoc 49 | */ 50 | public static $_parents = [ 51 | OpenApi::class, 52 | Tag::class, 53 | Schema::class, 54 | AdditionalProperties::class, 55 | Property::class, 56 | Operation::class, 57 | Get::class, 58 | Post::class, 59 | Put::class, 60 | Delete::class, 61 | Patch::class, 62 | Head::class, 63 | Options::class, 64 | Trace::class, 65 | Items::class, 66 | JsonContent::class, 67 | XmlContent::class, 68 | ]; 69 | 70 | /** 71 | * @inheritdoc 72 | */ 73 | public static $_nested = [ 74 | Attachable::class => ['attachables'], 75 | ]; 76 | } 77 | -------------------------------------------------------------------------------- /src/Annotations/Flow.php: -------------------------------------------------------------------------------- 1 | ['implicit', 'password', 'authorizationCode', 'clientCredentials'], 75 | 'refreshUrl' => 'string', 76 | 'authorizationUrl' => 'string', 77 | 'tokenUrl' => 'string', 78 | ]; 79 | 80 | /** 81 | * @inheritdoc 82 | */ 83 | public static $_parents = [ 84 | SecurityScheme::class, 85 | ]; 86 | 87 | /** 88 | * @inheritdoc 89 | */ 90 | public static $_nested = [ 91 | Attachable::class => ['attachables'], 92 | ]; 93 | 94 | /** 95 | * @inheritdoc 96 | */ 97 | #[\ReturnTypeWillChange] 98 | public function jsonSerialize() 99 | { 100 | if ($this->scopes === []) { 101 | $this->scopes = new \stdClass(); 102 | } 103 | 104 | return parent::jsonSerialize(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Annotations/Get.php: -------------------------------------------------------------------------------- 1 | 'string', 83 | 'description' => 'string', 84 | ]; 85 | 86 | /** 87 | * @inheritdoc 88 | */ 89 | public static $_nested = [ 90 | Schema::class => 'schema', 91 | Attachable::class => ['attachables'], 92 | ]; 93 | 94 | /** 95 | * @inheritdoc 96 | */ 97 | public static $_parents = [ 98 | Components::class, 99 | Response::class, 100 | ]; 101 | } 102 | -------------------------------------------------------------------------------- /src/Annotations/HeaderParameter.php: -------------------------------------------------------------------------------- 1 | @OA\Request header parameter. 13 | * 14 | * @Annotation 15 | */ 16 | class HeaderParameter extends Parameter 17 | { 18 | /** 19 | * @inheritdoc 20 | * This takes 'header' as the default location. 21 | */ 22 | public $in = 'header'; 23 | } 24 | -------------------------------------------------------------------------------- /src/Annotations/Info.php: -------------------------------------------------------------------------------- 1 | 'string', 78 | 'version' => 'string', 79 | 'description' => 'string', 80 | 'termsOfService' => 'string', 81 | ]; 82 | 83 | /** 84 | * @inheritdoc 85 | */ 86 | public static $_nested = [ 87 | Contact::class => 'contact', 88 | License::class => 'license', 89 | Attachable::class => ['attachables'], 90 | ]; 91 | 92 | /** 93 | * @inheritdoc 94 | */ 95 | public static $_parents = [ 96 | OpenApi::class, 97 | ]; 98 | } 99 | -------------------------------------------------------------------------------- /src/Annotations/Items.php: -------------------------------------------------------------------------------- 1 | array. 11 | * 12 | * @Annotation 13 | */ 14 | class Items extends Schema 15 | { 16 | /** 17 | * @inheritdoc 18 | */ 19 | public static $_nested = [ 20 | Discriminator::class => 'discriminator', 21 | Items::class => 'items', 22 | Property::class => ['properties', 'property'], 23 | ExternalDocumentation::class => 'externalDocs', 24 | Xml::class => 'xml', 25 | AdditionalProperties::class => 'additionalProperties', 26 | Attachable::class => ['attachables'], 27 | ]; 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | public static $_parents = [ 33 | Property::class, 34 | AdditionalProperties::class, 35 | Schema::class, 36 | JsonContent::class, 37 | XmlContent::class, 38 | Items::class, 39 | ]; 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool 45 | { 46 | if (in_array($this, $skip, true)) { 47 | return true; 48 | } 49 | 50 | $valid = parent::validate($stack, $skip, $ref, $context); 51 | 52 | $parent = end($stack); 53 | // type might be array in 3.1.0 54 | if ($parent instanceof Schema && ($parent->type !== 'array' && !(is_array($parent->type) && in_array('array', $parent->type)))) { 55 | $this->_context->logger->warning('@OA\\Items() parent type must be "array" in ' . $this->_context); 56 | $valid = false; 57 | } 58 | 59 | return $valid; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Annotations/JsonContent.php: -------------------------------------------------------------------------------- 1 | 'discriminator', 44 | Items::class => 'items', 45 | Property::class => ['properties', 'property'], 46 | ExternalDocumentation::class => 'externalDocs', 47 | AdditionalProperties::class => 'additionalProperties', 48 | Examples::class => ['examples', 'example'], 49 | Attachable::class => ['attachables'], 50 | ]; 51 | } 52 | -------------------------------------------------------------------------------- /src/Annotations/License.php: -------------------------------------------------------------------------------- 1 | identifier field is mutually exclusive of the url field. 29 | * 30 | * @var string 31 | */ 32 | public $identifier = Generator::UNDEFINED; 33 | 34 | /** 35 | * A URL to the license used for the API. This MUST be in the form of a URL. 36 | * 37 | * The url field is mutually exclusive of the identifier field. 38 | * 39 | * @var string 40 | */ 41 | public $url = Generator::UNDEFINED; 42 | 43 | /** 44 | * @inheritdoc 45 | */ 46 | public static $_types = [ 47 | 'name' => 'string', 48 | 'identifier' => 'string', 49 | 'url' => 'string', 50 | ]; 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | public static $_required = ['name']; 56 | 57 | /** 58 | * @inheritdoc 59 | */ 60 | public static $_parents = [ 61 | Info::class, 62 | ]; 63 | 64 | /** 65 | * @inheritdoc 66 | */ 67 | public static $_nested = [ 68 | Attachable::class => ['attachables'], 69 | ]; 70 | 71 | /** 72 | * @inheritdoc 73 | */ 74 | #[\ReturnTypeWillChange] 75 | public function jsonSerialize() 76 | { 77 | $data = parent::jsonSerialize(); 78 | 79 | if ($this->_context->isVersion(OpenApi::VERSION_3_0_0)) { 80 | unset($data->identifier); 81 | } 82 | 83 | return $data; 84 | } 85 | 86 | /** 87 | * @inheritdoc 88 | */ 89 | public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool 90 | { 91 | $valid = parent::validate($stack, $skip, $ref, $context); 92 | 93 | if ($this->_context->isVersion(OpenApi::VERSION_3_1_0)) { 94 | if (!Generator::isDefault($this->url) && $this->identifier !== Generator::UNDEFINED) { 95 | $this->_context->logger->warning($this->identity() . ' url and identifier are mutually exclusive'); 96 | $valid = false; 97 | } 98 | } 99 | 100 | return $valid; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Annotations/Link.php: -------------------------------------------------------------------------------- 1 | links array. 38 | * 39 | * @var string 40 | */ 41 | public $link = Generator::UNDEFINED; 42 | 43 | /** 44 | * A relative or absolute reference to an OA operation. 45 | * 46 | * This field is mutually exclusive of the operationId field, and must point to an Operation object. 47 | * 48 | * Relative values may be used to locate an existing Operation object in the OpenAPI definition. 49 | * 50 | * @var string 51 | */ 52 | public $operationRef = Generator::UNDEFINED; 53 | 54 | /** 55 | * The name of an existing, resolvable OA operation, as defined with a unique operationId. 56 | * 57 | * This field is mutually exclusive of the operationRef field. 58 | * 59 | * @var string 60 | */ 61 | public $operationId = Generator::UNDEFINED; 62 | 63 | /** 64 | * A map representing parameters to pass to an operation as specified with operationId or identified via 65 | * operationRef. 66 | * 67 | * The key is the parameter name to be used, whereas the value can be a constant or an expression to 68 | * be evaluated and passed to the linked operation. 69 | * The parameter name can be qualified using the parameter location [{in}.]{name} for operations 70 | * that use the same parameter name in different locations (e.g. path.id). 71 | * 72 | * @var array 73 | */ 74 | public $parameters = Generator::UNDEFINED; 75 | 76 | /** 77 | * A literal value or {expression} to use as a request body when calling the target operation. 78 | */ 79 | public $requestBody = Generator::UNDEFINED; 80 | 81 | /** 82 | * A description of the link. 83 | * 84 | * CommonMark syntax may be used for rich text representation. 85 | * 86 | * @var string 87 | */ 88 | public $description = Generator::UNDEFINED; 89 | 90 | /** 91 | * A server object to be used by the target operation. 92 | * 93 | * @var Server 94 | */ 95 | public $server = Generator::UNDEFINED; 96 | 97 | /** 98 | * @inheritdoc 99 | */ 100 | public static $_nested = [ 101 | Server::class => 'server', 102 | Attachable::class => ['attachables'], 103 | ]; 104 | 105 | /** 106 | * @inheritdoc 107 | */ 108 | public static $_parents = [ 109 | Components::class, 110 | Response::class, 111 | ]; 112 | } 113 | -------------------------------------------------------------------------------- /src/Annotations/MediaType.php: -------------------------------------------------------------------------------- 1 | content array. 22 | * 23 | * @var string 24 | */ 25 | public $mediaType = Generator::UNDEFINED; 26 | 27 | /** 28 | * The schema defining the type used for the request body. 29 | * 30 | * @var Schema 31 | */ 32 | public $schema = Generator::UNDEFINED; 33 | 34 | /** 35 | * Example of the media type. 36 | * 37 | * The example object should be in the correct format as specified by the media type. 38 | * The example object is mutually exclusive of the examples object. 39 | * 40 | * Furthermore, if referencing a schema which contains an example, 41 | * the example value shall override the example provided by the schema. 42 | */ 43 | public $example = Generator::UNDEFINED; 44 | 45 | /** 46 | * Examples of the media type. 47 | * 48 | * Each example should contain a value in the correct format as specified in the parameter encoding. 49 | * The examples object is mutually exclusive of the example object. 50 | * Furthermore, if referencing a schema which contains an example, the examples value shall override the example provided by the schema. 51 | * 52 | * @var array 53 | */ 54 | public $examples = Generator::UNDEFINED; 55 | 56 | /** 57 | * A map between a property name and its encoding information. 58 | * 59 | * The key, being the property name, must exist in the schema as a property. 60 | * 61 | * The encoding object shall only apply to requestBody objects when the media type is multipart or 62 | * application/x-www-form-urlencoded. 63 | * 64 | * @var array 65 | */ 66 | public $encoding = Generator::UNDEFINED; 67 | 68 | /** 69 | * @inheritdoc 70 | */ 71 | public static $_nested = [ 72 | Schema::class => 'schema', 73 | Examples::class => ['examples', 'example'], 74 | Attachable::class => ['attachables'], 75 | ]; 76 | 77 | /** 78 | * @inheritdoc 79 | */ 80 | public static $_parents = [ 81 | Response::class, 82 | RequestBody::class, 83 | ]; 84 | } 85 | -------------------------------------------------------------------------------- /src/Annotations/Options.php: -------------------------------------------------------------------------------- 1 | paths array). 46 | * 47 | * @var string 48 | */ 49 | public $path = Generator::UNDEFINED; 50 | 51 | /** 52 | * A definition of a GET operation on this path. 53 | * 54 | * @var Get 55 | */ 56 | public $get = Generator::UNDEFINED; 57 | 58 | /** 59 | * A definition of a PUT operation on this path. 60 | * 61 | * @var Put 62 | */ 63 | public $put = Generator::UNDEFINED; 64 | 65 | /** 66 | * A definition of a POST operation on this path. 67 | * 68 | * @var Post 69 | */ 70 | public $post = Generator::UNDEFINED; 71 | 72 | /** 73 | * A definition of a DELETE operation on this path. 74 | * 75 | * @var Delete 76 | */ 77 | public $delete = Generator::UNDEFINED; 78 | 79 | /** 80 | * A definition of a OPTIONS operation on this path. 81 | * 82 | * @var Options 83 | */ 84 | public $options = Generator::UNDEFINED; 85 | 86 | /** 87 | * A definition of a HEAD operation on this path. 88 | * 89 | * @var Head 90 | */ 91 | public $head = Generator::UNDEFINED; 92 | 93 | /** 94 | * A definition of a PATCH operation on this path. 95 | * 96 | * @var Patch 97 | */ 98 | public $patch = Generator::UNDEFINED; 99 | 100 | /** 101 | * A definition of a TRACE operation on this path. 102 | * 103 | * @var Trace 104 | */ 105 | public $trace = Generator::UNDEFINED; 106 | 107 | /** 108 | * An alternative server array to service all operations in this path. 109 | * 110 | * @var Server[] 111 | */ 112 | public $servers = Generator::UNDEFINED; 113 | 114 | /** 115 | * A list of parameters that are applicable for all the operations described under this path. 116 | * 117 | * These parameters can be overridden at the operation level, but cannot be removed there. 118 | * The list must not include duplicated parameters. 119 | * A unique parameter is defined by a combination of a name and location. 120 | * The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object's components/parameters. 121 | * 122 | * @var Parameter[] 123 | */ 124 | public $parameters = Generator::UNDEFINED; 125 | 126 | /** 127 | * @inheritdoc 128 | */ 129 | public static $_types = [ 130 | 'path' => 'string', 131 | 'summary' => 'string', 132 | ]; 133 | 134 | /** 135 | * @inheritdoc 136 | */ 137 | public static $_nested = [ 138 | Get::class => 'get', 139 | Post::class => 'post', 140 | Put::class => 'put', 141 | Delete::class => 'delete', 142 | Patch::class => 'patch', 143 | Trace::class => 'trace', 144 | Head::class => 'head', 145 | Options::class => 'options', 146 | Parameter::class => ['parameters'], 147 | PathParameter::class => ['parameters'], 148 | Server::class => ['servers'], 149 | Attachable::class => ['attachables'], 150 | ]; 151 | 152 | /** 153 | * @inheritdoc 154 | */ 155 | public static $_parents = [ 156 | OpenApi::class, 157 | ]; 158 | 159 | /** 160 | * Returns a list of all operations (all methods) for this path item. 161 | * 162 | * @return Operation[] 163 | */ 164 | public function operations(): array 165 | { 166 | $operations = []; 167 | foreach (PathItem::$_nested as $className => $property) { 168 | if (is_subclass_of($className, Operation::class) && !Generator::isDefault($this->{$property})) { 169 | $operations[] = $this->{$property}; 170 | } 171 | } 172 | 173 | return $operations; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Annotations/PathParameter.php: -------------------------------------------------------------------------------- 1 | @OA\Request path parameter. 13 | * 14 | * @Annotation 15 | */ 16 | class PathParameter extends Parameter 17 | { 18 | /** 19 | * @inheritdoc 20 | * This takes 'path' as the default location. 21 | */ 22 | public $in = 'path'; 23 | 24 | /** 25 | * @inheritdoc 26 | */ 27 | public $required = true; 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | public static $_required = ['name']; 33 | } 34 | -------------------------------------------------------------------------------- /src/Annotations/Post.php: -------------------------------------------------------------------------------- 1 | properties array. 18 | * 19 | * @var string 20 | */ 21 | public $property = Generator::UNDEFINED; 22 | 23 | /** 24 | * @inheritdoc 25 | */ 26 | public static $_parents = [ 27 | AdditionalProperties::class, 28 | Schema::class, 29 | JsonContent::class, 30 | XmlContent::class, 31 | Property::class, 32 | Items::class, 33 | ]; 34 | 35 | /** 36 | * @inheritdoc 37 | */ 38 | public static $_nested = [ 39 | Discriminator::class => 'discriminator', 40 | Items::class => 'items', 41 | Property::class => ['properties', 'property'], 42 | ExternalDocumentation::class => 'externalDocs', 43 | Xml::class => 'xml', 44 | AdditionalProperties::class => 'additionalProperties', 45 | Attachable::class => ['attachables'], 46 | ]; 47 | } 48 | -------------------------------------------------------------------------------- /src/Annotations/Put.php: -------------------------------------------------------------------------------- 1 | @OA\Request query parameter. 13 | * 14 | * @Annotation 15 | */ 16 | class QueryParameter extends Parameter 17 | { 18 | /** 19 | * @inheritdoc 20 | * This takes 'query' as the default location. 21 | */ 22 | public $in = 'query'; 23 | } 24 | -------------------------------------------------------------------------------- /src/Annotations/RequestBody.php: -------------------------------------------------------------------------------- 1 | requestBodies array. 33 | * 34 | * @var string 35 | */ 36 | public $request = Generator::UNDEFINED; 37 | 38 | /** 39 | * A brief description of the parameter. 40 | * 41 | * This could contain examples of use. 42 | * 43 | * CommonMark syntax may be used for rich text representation. 44 | * 45 | * @var string 46 | */ 47 | public $description = Generator::UNDEFINED; 48 | 49 | /** 50 | * Determines whether this parameter is mandatory. 51 | * 52 | * If the parameter location is "path", this property is required and its value must be true. 53 | * Otherwise, the property may be included and its default value is false. 54 | * 55 | * @var bool 56 | */ 57 | public $required = Generator::UNDEFINED; 58 | 59 | /** 60 | * The content of the request body. 61 | * 62 | * The key is a media type or media type range and the value describes it. For requests that match multiple keys, 63 | * only the most specific key is applicable. e.g. text/plain overrides text/*. 64 | * 65 | * @var array|MediaType|JsonContent|XmlContent|Attachable 66 | */ 67 | public $content = Generator::UNDEFINED; 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public static $_types = [ 73 | 'description' => 'string', 74 | 'required' => 'boolean', 75 | 'request' => 'string', 76 | ]; 77 | 78 | public static $_parents = [ 79 | Components::class, 80 | Delete::class, 81 | Get::class, 82 | Head::class, 83 | Operation::class, 84 | Options::class, 85 | Patch::class, 86 | Post::class, 87 | Trace::class, 88 | Put::class, 89 | ]; 90 | 91 | /** 92 | * @inheritdoc 93 | */ 94 | public static $_nested = [ 95 | MediaType::class => ['content', 'mediaType'], 96 | Attachable::class => ['attachables'], 97 | ]; 98 | 99 | /** 100 | * @inheritdoc 101 | */ 102 | #[\ReturnTypeWillChange] 103 | public function jsonSerialize() 104 | { 105 | $data = parent::jsonSerialize(); 106 | 107 | unset($data->request); 108 | 109 | return $data; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Annotations/Response.php: -------------------------------------------------------------------------------- 1 | responses array. 32 | * 33 | * A HTTP status code or default. 34 | * 35 | * @var string|int 36 | */ 37 | public $response = Generator::UNDEFINED; 38 | 39 | /** 40 | * A short description of the response. 41 | * 42 | * CommonMark syntax may be used for rich text representation. 43 | * 44 | * @var string 45 | */ 46 | public $description = Generator::UNDEFINED; 47 | 48 | /** 49 | * Maps a header name to its definition. 50 | * 51 | * RFC7230 states header names are case-insensitive. 52 | * 53 | * If a response header is defined with the name "Content-Type", it shall be ignored. 54 | * 55 | * @see [RFC7230](https://tools.ietf.org/html/rfc7230#page-22) 56 | * 57 | * @var Header[] 58 | */ 59 | public $headers = Generator::UNDEFINED; 60 | 61 | /** 62 | * A map containing descriptions of potential response payloads. 63 | * 64 | * The key is a media type or media type range and the value describes it. 65 | * 66 | * For responses that match multiple keys, only the most specific key is applicable; 67 | * e.g. text/plain overrides text/*. 68 | * 69 | * @var MediaType|JsonContent|XmlContent|Attachable|array 70 | */ 71 | public $content = Generator::UNDEFINED; 72 | 73 | /** 74 | * A map of operations links that can be followed from the response. 75 | * 76 | * The key of the map is a short name for the link, following the naming constraints of the names for Component 77 | * Objects. 78 | * 79 | * @var Link[] 80 | */ 81 | public $links = Generator::UNDEFINED; 82 | 83 | /** 84 | * @inheritdoc 85 | */ 86 | public static $_types = [ 87 | 'description' => 'string', 88 | ]; 89 | 90 | /** 91 | * @inheritdoc 92 | */ 93 | public static $_nested = [ 94 | MediaType::class => ['content', 'mediaType'], 95 | Header::class => ['headers', 'header'], 96 | Link::class => ['links', 'link'], 97 | Attachable::class => ['attachables'], 98 | ]; 99 | 100 | /** 101 | * @inheritdoc 102 | */ 103 | public static $_parents = [ 104 | Components::class, 105 | Operation::class, 106 | Get::class, 107 | Post::class, 108 | Put::class, 109 | Patch::class, 110 | Delete::class, 111 | Head::class, 112 | Options::class, 113 | Trace::class, 114 | ]; 115 | 116 | /** 117 | * @inheritdoc 118 | */ 119 | public function validate(array $stack = [], array $skip = [], string $ref = '', $context = null): bool 120 | { 121 | $valid = parent::validate($stack, $skip, $ref, $context); 122 | 123 | if (Generator::isDefault($this->description) && Generator::isDefault($this->ref)) { 124 | $this->_context->logger->warning($this->identity() . ' One of description or ref is required in ' . $this->_context->getDebugLocation()); 125 | $valid = false; 126 | } 127 | 128 | return $valid; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Annotations/SecurityScheme.php: -------------------------------------------------------------------------------- 1 | security array. 29 | * 30 | * @var string 31 | */ 32 | public $securityScheme = Generator::UNDEFINED; 33 | 34 | /** 35 | * The type of the security scheme. 36 | * 37 | * @var string|non-empty-array 38 | */ 39 | public $type = Generator::UNDEFINED; 40 | 41 | /** 42 | * A short description for security scheme. 43 | * 44 | * @var string 45 | */ 46 | public $description = Generator::UNDEFINED; 47 | 48 | /** 49 | * The name of the header or query parameter to be used. 50 | * 51 | * @var string 52 | */ 53 | public $name = Generator::UNDEFINED; 54 | 55 | /** 56 | * Required The location of the API key. 57 | * 58 | * @var string 59 | */ 60 | public $in = Generator::UNDEFINED; 61 | 62 | /** 63 | * The flow used by the OAuth2 security scheme. 64 | * 65 | * @var Flow[] 66 | */ 67 | public $flows = Generator::UNDEFINED; 68 | 69 | /** 70 | * A hint to the client to identify how the bearer token is formatted. 71 | * 72 | * Bearer tokens are usually generated by an authorization server, so this information is primarily for documentation purposes. 73 | * 74 | * @var string 75 | */ 76 | public $bearerFormat = Generator::UNDEFINED; 77 | 78 | /** 79 | * The name of the HTTP Authorization scheme. 80 | * 81 | * @see [RFC7235](https://tools.ietf.org/html/rfc7235#section-5.1) 82 | * 83 | * @var string 84 | */ 85 | public $scheme = Generator::UNDEFINED; 86 | 87 | /** 88 | * OpenId Connect URL to discover OAuth2 configuration values. This MUST be in the form of a URL. 89 | * 90 | * @var string 91 | */ 92 | public $openIdConnectUrl = Generator::UNDEFINED; 93 | 94 | /** 95 | * @inheritdoc 96 | */ 97 | public static $_required = ['securityScheme', 'type']; 98 | 99 | /** 100 | * @inheritdoc 101 | */ 102 | public static $_types = [ 103 | 'type' => ['http', 'apiKey', 'oauth2', 'openIdConnect'], 104 | 'description' => 'string', 105 | 'name' => 'string', 106 | 'bearerFormat' => 'string', 107 | 'in' => ['query', 'header', 'cookie'], 108 | ]; 109 | 110 | /** 111 | * @inheritdoc 112 | */ 113 | public static $_nested = [ 114 | Flow::class => ['flows', 'flow'], 115 | Attachable::class => ['attachables'], 116 | ]; 117 | 118 | /** 119 | * @inheritdoc 120 | */ 121 | public static $_parents = [ 122 | Components::class, 123 | ]; 124 | 125 | /** 126 | * @inheritdoc 127 | */ 128 | public function merge(array $annotations, bool $ignore = false): array 129 | { 130 | $unmerged = parent::merge($annotations, $ignore); 131 | 132 | if ($this->type === 'oauth2') { 133 | $this->name = Generator::UNDEFINED; 134 | } 135 | 136 | return $unmerged; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Annotations/Server.php: -------------------------------------------------------------------------------- 1 | ['variables', 'serverVariable'], 72 | Attachable::class => ['attachables'], 73 | ]; 74 | 75 | /** 76 | * @inheritdoc 77 | */ 78 | public static $_required = ['url']; 79 | 80 | /** 81 | * @inheritdoc 82 | */ 83 | public static $_types = [ 84 | 'url' => 'string', 85 | 'description' => 'string', 86 | ]; 87 | } 88 | -------------------------------------------------------------------------------- /src/Annotations/ServerVariable.php: -------------------------------------------------------------------------------- 1 | variables array. 22 | * 23 | * @var string 24 | */ 25 | public $serverVariable = Generator::UNDEFINED; 26 | 27 | /** 28 | * An enumeration of values to be used if the substitution options are from a limited set. 29 | * 30 | * @var array|class-string 31 | */ 32 | public $enum = Generator::UNDEFINED; 33 | 34 | /** 35 | * The default value to use for substitution, and to send, if an alternate value is not supplied. 36 | * 37 | * Unlike the Schema Object's default, this value must be provided by the consumer. 38 | * 39 | * @var string 40 | */ 41 | public $default = Generator::UNDEFINED; 42 | 43 | /** 44 | * A map between a variable name and its value. 45 | * 46 | * The value is used for substitution in the server's URL template. 47 | * 48 | * @var array 49 | */ 50 | public $variables = Generator::UNDEFINED; 51 | 52 | /** 53 | * An optional description for the server variable. 54 | * 55 | * CommonMark syntax MAY be used for rich text representation. 56 | * 57 | * @var string 58 | */ 59 | public $description = Generator::UNDEFINED; 60 | 61 | /** 62 | * @inheritdoc 63 | */ 64 | public static $_parents = [ 65 | Server::class, 66 | ]; 67 | 68 | /** 69 | * @inheritdoc 70 | */ 71 | public static $_required = ['default']; 72 | 73 | /** 74 | * @inheritdoc 75 | */ 76 | public static $_types = [ 77 | 'default' => 'string', 78 | 'description' => 'string', 79 | ]; 80 | 81 | /** 82 | * @inheritdoc 83 | */ 84 | public static $_nested = [ 85 | Attachable::class => ['attachables'], 86 | ]; 87 | } 88 | -------------------------------------------------------------------------------- /src/Annotations/Tag.php: -------------------------------------------------------------------------------- 1 | 'string', 49 | 'description' => 'string', 50 | ]; 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | public static $_parents = [ 56 | OpenApi::class, 57 | ]; 58 | 59 | /** 60 | * @inheritdoc 61 | */ 62 | public static $_nested = [ 63 | ExternalDocumentation::class => 'externalDocs', 64 | Attachable::class => ['attachables'], 65 | ]; 66 | } 67 | -------------------------------------------------------------------------------- /src/Annotations/Trace.php: -------------------------------------------------------------------------------- 1 | PathItem with the main difference being that it requires webhook instead of path. 13 | * 14 | * @Annotation 15 | */ 16 | class Webhook extends PathItem 17 | { 18 | /** 19 | * Key for the webhooks map. 20 | * 21 | * @var string 22 | */ 23 | public $webhook = Generator::UNDEFINED; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public static $_required = ['webhook']; 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public static $_parents = [ 34 | OpenApi::class, 35 | ]; 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | public static $_types = [ 41 | 'webhook' => 'string', 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /src/Annotations/Xml.php: -------------------------------------------------------------------------------- 1 | true. 24 | * 25 | * If wrapped is false, it will be ignored. 26 | * 27 | * @var string 28 | */ 29 | public $name = Generator::UNDEFINED; 30 | 31 | /** 32 | * The URL of the namespace definition. Value SHOULD be in the form of a URL. 33 | * 34 | * @var string 35 | */ 36 | public $namespace = Generator::UNDEFINED; 37 | 38 | /** 39 | * The prefix to be used for the name. 40 | * 41 | * @var string 42 | */ 43 | public $prefix = Generator::UNDEFINED; 44 | 45 | /** 46 | * Declares whether the property definition translates to an attribute instead of an element. 47 | * 48 | * Default value is false. 49 | * 50 | * @var bool 51 | */ 52 | public $attribute = Generator::UNDEFINED; 53 | 54 | /** 55 | * MAY be used only for an array definition. 56 | * 57 | * Signifies whether the array is wrapped (for example <books><book/><book/></books>) 58 | * or unwrapped (<book/><book/>). 59 | * 60 | * Default value is false. The definition takes effect only when defined alongside type being array (outside the items). 61 | * 62 | * @var bool 63 | */ 64 | public $wrapped = Generator::UNDEFINED; 65 | 66 | /** 67 | * @inheritdoc 68 | */ 69 | public static $_types = [ 70 | 'name' => 'string', 71 | 'namespace' => 'string', 72 | 'prefix' => 'string', 73 | 'attribute' => 'boolean', 74 | 'wrapped' => 'boolean', 75 | ]; 76 | 77 | /** 78 | * @inheritdoc 79 | */ 80 | public static $_parents = [ 81 | AdditionalProperties::class, 82 | Schema::class, 83 | Property::class, 84 | Schema::class, 85 | Items::class, 86 | XmlContent::class, 87 | ]; 88 | 89 | /** 90 | * @inheritdoc 91 | */ 92 | public static $_nested = [ 93 | Attachable::class => ['attachables'], 94 | ]; 95 | } 96 | -------------------------------------------------------------------------------- /src/Annotations/XmlContent.php: -------------------------------------------------------------------------------- 1 | @OA\Schema inside a Response and MediaType->'application/xml' will be generated. 15 | * 16 | * @Annotation 17 | */ 18 | class XmlContent extends Schema 19 | { 20 | /** 21 | * @inheritdoc 22 | */ 23 | public static $_parents = []; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public static $_nested = [ 29 | Discriminator::class => 'discriminator', 30 | Items::class => 'items', 31 | Property::class => ['properties', 'property'], 32 | ExternalDocumentation::class => 'externalDocs', 33 | Xml::class => 'xml', 34 | AdditionalProperties::class => 'additionalProperties', 35 | Examples::class => ['examples', 'example'], 36 | Attachable::class => ['attachables'], 37 | ]; 38 | } 39 | -------------------------------------------------------------------------------- /src/Attributes/AdditionalProperties.php: -------------------------------------------------------------------------------- 1 | |null $type 20 | * @param int|float $maximum 21 | * @param int|float $minimum 22 | * @param array|class-string|null $enum 23 | * @param array $allOf 24 | * @param array $anyOf 25 | * @param array $oneOf 26 | * @param array|null $x 27 | * @param Attachable[]|null $attachables 28 | */ 29 | public function __construct( 30 | // schema 31 | string|object|null $ref = null, 32 | ?string $schema = null, 33 | ?string $title = null, 34 | ?string $description = null, 35 | ?int $maxProperties = null, 36 | ?int $minProperties = null, 37 | ?array $required = null, 38 | ?array $properties = null, 39 | string|array|null $type = null, 40 | ?string $format = null, 41 | ?Items $items = null, 42 | ?string $collectionFormat = null, 43 | mixed $default = Generator::UNDEFINED, 44 | $maximum = null, 45 | bool|int|float|null $exclusiveMaximum = null, 46 | $minimum = null, 47 | bool|int|float|null $exclusiveMinimum = null, 48 | ?int $maxLength = null, 49 | ?int $minLength = null, 50 | ?int $maxItems = null, 51 | ?int $minItems = null, 52 | ?bool $uniqueItems = null, 53 | ?string $pattern = null, 54 | array|string|null $enum = null, 55 | ?Discriminator $discriminator = null, 56 | ?bool $readOnly = null, 57 | ?bool $writeOnly = null, 58 | ?Xml $xml = null, 59 | ?ExternalDocumentation $externalDocs = null, 60 | mixed $example = Generator::UNDEFINED, 61 | ?bool $nullable = null, 62 | ?bool $deprecated = null, 63 | ?array $allOf = null, 64 | ?array $anyOf = null, 65 | ?array $oneOf = null, 66 | AdditionalProperties|bool|null $additionalProperties = null, 67 | // annotation 68 | ?array $x = null, 69 | ?array $attachables = null 70 | ) { 71 | parent::__construct([ 72 | 'ref' => $ref ?? Generator::UNDEFINED, 73 | 'schema' => $schema ?? Generator::UNDEFINED, 74 | 'title' => $title ?? Generator::UNDEFINED, 75 | 'description' => $description ?? Generator::UNDEFINED, 76 | 'maxProperties' => $maxProperties ?? Generator::UNDEFINED, 77 | 'minProperties' => $minProperties ?? Generator::UNDEFINED, 78 | 'required' => $required ?? Generator::UNDEFINED, 79 | 'properties' => $properties ?? Generator::UNDEFINED, 80 | 'type' => $type ?? Generator::UNDEFINED, 81 | 'format' => $format ?? Generator::UNDEFINED, 82 | 'collectionFormat' => $collectionFormat ?? Generator::UNDEFINED, 83 | 'default' => $default, 84 | 'maximum' => $maximum ?? Generator::UNDEFINED, 85 | 'exclusiveMaximum' => $exclusiveMaximum ?? Generator::UNDEFINED, 86 | 'minimum' => $minimum ?? Generator::UNDEFINED, 87 | 'exclusiveMinimum' => $exclusiveMinimum ?? Generator::UNDEFINED, 88 | 'maxLength' => $maxLength ?? Generator::UNDEFINED, 89 | 'minLength' => $minLength ?? Generator::UNDEFINED, 90 | 'maxItems' => $maxItems ?? Generator::UNDEFINED, 91 | 'minItems' => $minItems ?? Generator::UNDEFINED, 92 | 'uniqueItems' => $uniqueItems ?? Generator::UNDEFINED, 93 | 'pattern' => $pattern ?? Generator::UNDEFINED, 94 | 'enum' => $enum ?? Generator::UNDEFINED, 95 | 'readOnly' => $readOnly ?? Generator::UNDEFINED, 96 | 'writeOnly' => $writeOnly ?? Generator::UNDEFINED, 97 | 'xml' => $xml ?? Generator::UNDEFINED, 98 | 'example' => $example, 99 | 'nullable' => $nullable ?? Generator::UNDEFINED, 100 | 'deprecated' => $deprecated ?? Generator::UNDEFINED, 101 | 'allOf' => $allOf ?? Generator::UNDEFINED, 102 | 'anyOf' => $anyOf ?? Generator::UNDEFINED, 103 | 'oneOf' => $oneOf ?? Generator::UNDEFINED, 104 | 'x' => $x ?? Generator::UNDEFINED, 105 | 'attachables' => $attachables ?? Generator::UNDEFINED, 106 | 'value' => $this->combine($items, $discriminator, $externalDocs, $additionalProperties), 107 | ]); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Attributes/Attachable.php: -------------------------------------------------------------------------------- 1 | |null $schemas 17 | * @param Response[]|null $responses 18 | * @param Parameter[]|null $parameters 19 | * @param RequestBody[]|null $requestBodies 20 | * @param array|null $examples 21 | * @param Header[]|null $headers 22 | * @param SecurityScheme[]|null $securitySchemes 23 | * @param Link[]|null $links 24 | * @param array|null $x 25 | * @param Attachable[]|null $attachables 26 | */ 27 | public function __construct( 28 | ?array $schemas = null, 29 | ?array $responses = null, 30 | ?array $parameters = null, 31 | ?array $requestBodies = null, 32 | ?array $examples = null, 33 | ?array $headers = null, 34 | ?array $securitySchemes = null, 35 | ?array $links = null, 36 | ?array $callbacks = null, 37 | // annotation 38 | ?array $x = null, 39 | ?array $attachables = null 40 | ) { 41 | parent::__construct([ 42 | 'callbacks' => $callbacks ?? Generator::UNDEFINED, 43 | 'x' => $x ?? Generator::UNDEFINED, 44 | 'attachables' => $attachables ?? Generator::UNDEFINED, 45 | 'value' => $this->combine($schemas, $responses, $parameters, $examples, $requestBodies, $headers, $securitySchemes, $links), 46 | ]); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Attributes/Contact.php: -------------------------------------------------------------------------------- 1 | |null $x 17 | * @param Attachable[]|null $attachables 18 | */ 19 | public function __construct( 20 | ?string $name = null, 21 | ?string $url = null, 22 | ?string $email = null, 23 | // annotation 24 | ?array $x = null, 25 | ?array $attachables = null 26 | ) { 27 | parent::__construct([ 28 | 'name' => $name ?? Generator::UNDEFINED, 29 | 'url' => $url ?? Generator::UNDEFINED, 30 | 'email' => $email ?? Generator::UNDEFINED, 31 | 'x' => $x ?? Generator::UNDEFINED, 32 | 'attachables' => $attachables ?? Generator::UNDEFINED, 33 | ]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Attributes/CookieParameter.php: -------------------------------------------------------------------------------- 1 | |null $x 18 | * @param Attachable[]|null $attachables 19 | */ 20 | public function __construct( 21 | ?string $propertyName = null, 22 | ?array $mapping = null, 23 | // annotation 24 | ?array $x = null, 25 | ?array $attachables = null 26 | ) { 27 | parent::__construct([ 28 | 'propertyName' => $propertyName ?? Generator::UNDEFINED, 29 | 'mapping' => $mapping ?? Generator::UNDEFINED, 30 | 'x' => $x ?? Generator::UNDEFINED, 31 | 'attachables' => $attachables ?? Generator::UNDEFINED, 32 | ]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Attributes/Examples.php: -------------------------------------------------------------------------------- 1 | |null $x 18 | * @param Attachable[]|null $attachables 19 | */ 20 | public function __construct( 21 | ?string $example = null, 22 | ?string $summary = null, 23 | ?string $description = null, 24 | int|string|array|null $value = null, 25 | ?string $externalValue = null, 26 | string|object|null $ref = null, 27 | // annotation 28 | ?array $x = null, 29 | ?array $attachables = null 30 | ) { 31 | parent::__construct([ 32 | 'example' => $example ?? Generator::UNDEFINED, 33 | 'summary' => $summary ?? Generator::UNDEFINED, 34 | 'description' => $description ?? Generator::UNDEFINED, 35 | 'value' => $value ?? Generator::UNDEFINED, 36 | 'externalValue' => $externalValue ?? Generator::UNDEFINED, 37 | 'ref' => $ref ?? Generator::UNDEFINED, 38 | 'x' => $x ?? Generator::UNDEFINED, 39 | 'attachables' => $attachables ?? Generator::UNDEFINED, 40 | ]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Attributes/ExternalDocumentation.php: -------------------------------------------------------------------------------- 1 | |null $x 17 | * @param Attachable[]|null $attachables 18 | */ 19 | public function __construct( 20 | ?string $description = null, 21 | ?string $url = null, 22 | // annotation 23 | ?array $x = null, 24 | ?array $attachables = null 25 | ) { 26 | parent::__construct([ 27 | 'description' => $description ?? Generator::UNDEFINED, 28 | 'url' => $url ?? Generator::UNDEFINED, 29 | 'x' => $x ?? Generator::UNDEFINED, 30 | 'attachables' => $attachables ?? Generator::UNDEFINED, 31 | ]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Attributes/Flow.php: -------------------------------------------------------------------------------- 1 | |null $x 18 | * @param Attachable[]|null $attachables 19 | */ 20 | public function __construct( 21 | ?string $authorizationUrl = null, 22 | ?string $tokenUrl = null, 23 | ?string $refreshUrl = null, 24 | ?string $flow = null, 25 | ?array $scopes = null, 26 | // annotation 27 | ?array $x = null, 28 | ?array $attachables = null 29 | ) { 30 | parent::__construct([ 31 | 'authorizationUrl' => $authorizationUrl ?? Generator::UNDEFINED, 32 | 'tokenUrl' => $tokenUrl ?? Generator::UNDEFINED, 33 | 'refreshUrl' => $refreshUrl ?? Generator::UNDEFINED, 34 | 'flow' => $flow ?? Generator::UNDEFINED, 35 | 'scopes' => $scopes ?? Generator::UNDEFINED, 36 | 'x' => $x ?? Generator::UNDEFINED, 37 | 'attachables' => $attachables ?? Generator::UNDEFINED, 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Attributes/Get.php: -------------------------------------------------------------------------------- 1 | |null $x 17 | * @param Attachable[]|null $attachables 18 | */ 19 | public function __construct( 20 | string|object|null $ref = null, 21 | ?string $header = null, 22 | ?string $description = null, 23 | ?bool $required = null, 24 | ?Schema $schema = null, 25 | ?bool $deprecated = null, 26 | ?bool $allowEmptyValue = null, 27 | // annotation4 28 | ?array $x = null, 29 | ?array $attachables = null 30 | ) { 31 | parent::__construct([ 32 | 'ref' => $ref ?? Generator::UNDEFINED, 33 | 'header' => $header ?? Generator::UNDEFINED, 34 | 'description' => $description ?? Generator::UNDEFINED, 35 | 'required' => $required ?? Generator::UNDEFINED, 36 | 'deprecated' => $deprecated ?? Generator::UNDEFINED, 37 | 'allowEmptyValue' => $allowEmptyValue ?? Generator::UNDEFINED, 38 | 'x' => $x ?? Generator::UNDEFINED, 39 | 'attachables' => $attachables ?? Generator::UNDEFINED, 40 | 'value' => $this->combine($schema), 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Attributes/HeaderParameter.php: -------------------------------------------------------------------------------- 1 | |null $x 17 | * @param Attachable[]|null $attachables 18 | */ 19 | public function __construct( 20 | ?string $version = null, 21 | ?string $description = null, 22 | ?string $title = null, 23 | ?string $termsOfService = null, 24 | ?Contact $contact = null, 25 | ?License $license = null, 26 | // annotation 27 | ?array $x = null, 28 | ?array $attachables = null 29 | ) { 30 | parent::__construct([ 31 | 'version' => $version ?? Generator::UNDEFINED, 32 | 'description' => $description ?? Generator::UNDEFINED, 33 | 'title' => $title ?? Generator::UNDEFINED, 34 | 'termsOfService' => $termsOfService ?? Generator::UNDEFINED, 35 | 'x' => $x ?? Generator::UNDEFINED, 36 | 'attachables' => $attachables ?? Generator::UNDEFINED, 37 | 'value' => $this->combine($contact, $license), 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Attributes/License.php: -------------------------------------------------------------------------------- 1 | |null $x 17 | * @param Attachable[]|null $attachables 18 | */ 19 | public function __construct( 20 | ?string $name = null, 21 | ?string $identifier = null, 22 | ?string $url = null, 23 | // annotation 24 | ?array $x = null, 25 | ?array $attachables = null 26 | ) { 27 | parent::__construct([ 28 | 'name' => $name ?? Generator::UNDEFINED, 29 | 'identifier' => $identifier ?? Generator::UNDEFINED, 30 | 'url' => $url ?? Generator::UNDEFINED, 31 | 'x' => $x ?? Generator::UNDEFINED, 32 | 'attachables' => $attachables ?? Generator::UNDEFINED, 33 | ]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Attributes/Link.php: -------------------------------------------------------------------------------- 1 | $parameters 18 | * @param array|null $x 19 | * @param Attachable[]|null $attachables 20 | */ 21 | public function __construct( 22 | ?string $link = null, 23 | ?string $operationRef = null, 24 | string|object|null $ref = null, 25 | ?string $operationId = null, 26 | ?array $parameters = null, 27 | mixed $requestBody = null, 28 | ?string $description = null, 29 | ?Server $server = null, 30 | // annotation 31 | ?array $x = null, 32 | ?array $attachables = null 33 | ) { 34 | parent::__construct([ 35 | 'link' => $link ?? Generator::UNDEFINED, 36 | 'operationRef' => $operationRef ?? Generator::UNDEFINED, 37 | 'ref' => $ref ?? Generator::UNDEFINED, 38 | 'operationId' => $operationId ?? Generator::UNDEFINED, 39 | 'parameters' => $parameters ?? Generator::UNDEFINED, 40 | 'requestBody' => $requestBody ?? Generator::UNDEFINED, 41 | 'description' => $description ?? Generator::UNDEFINED, 42 | 'x' => $x ?? Generator::UNDEFINED, 43 | 'attachables' => $attachables ?? Generator::UNDEFINED, 44 | 'value' => $this->combine($server), 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Attributes/MediaType.php: -------------------------------------------------------------------------------- 1 | $examples 17 | * @param array $encoding 18 | * @param array|null $x 19 | * @param Attachable[]|null $attachables 20 | */ 21 | public function __construct( 22 | ?string $mediaType = null, 23 | ?Schema $schema = null, 24 | mixed $example = Generator::UNDEFINED, 25 | ?array $examples = null, 26 | ?array $encoding = null, 27 | // annotation 28 | ?array $x = null, 29 | ?array $attachables = null 30 | ) { 31 | parent::__construct([ 32 | 'mediaType' => $mediaType ?? Generator::UNDEFINED, 33 | 'example' => $example, 34 | 'encoding' => $encoding ?? Generator::UNDEFINED, 35 | 'x' => $x ?? Generator::UNDEFINED, 36 | 'attachables' => $attachables ?? Generator::UNDEFINED, 37 | 'value' => $this->combine($schema, $examples), 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Attributes/OpenApi.php: -------------------------------------------------------------------------------- 1 | |null $x 22 | * @param Attachable[]|null $attachables 23 | */ 24 | public function __construct( 25 | string $openapi = self::DEFAULT_VERSION, 26 | ?Info $info = null, 27 | ?array $servers = null, 28 | ?array $security = null, 29 | ?array $tags = null, 30 | ?ExternalDocumentation $externalDocs = null, 31 | ?array $paths = null, 32 | ?Components $components = null, 33 | ?array $webhooks = null, 34 | // annotation 35 | ?array $x = null, 36 | ?array $attachables = null 37 | ) { 38 | parent::__construct([ 39 | 'openapi' => $openapi, 40 | 'security' => $security ?? Generator::UNDEFINED, 41 | 'x' => $x ?? Generator::UNDEFINED, 42 | 'attachables' => $attachables ?? Generator::UNDEFINED, 43 | 'value' => $this->combine($info, $servers, $tags, $externalDocs, $paths, $components, $webhooks), 44 | ]); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Attributes/OperationTrait.php: -------------------------------------------------------------------------------- 1 | |null $x 19 | * @param Attachable[]|null $attachables 20 | */ 21 | public function __construct( 22 | ?string $path = null, 23 | ?string $operationId = null, 24 | ?string $description = null, 25 | ?string $summary = null, 26 | ?array $security = null, 27 | ?array $servers = null, 28 | ?RequestBody $requestBody = null, 29 | ?array $tags = null, 30 | ?array $parameters = null, 31 | ?array $responses = null, 32 | ?array $callbacks = null, 33 | ?ExternalDocumentation $externalDocs = null, 34 | ?bool $deprecated = null, 35 | // annotation 36 | ?array $x = null, 37 | ?array $attachables = null 38 | ) { 39 | parent::__construct([ 40 | 'path' => $path ?? Generator::UNDEFINED, 41 | 'operationId' => $operationId ?? Generator::UNDEFINED, 42 | 'description' => $description ?? Generator::UNDEFINED, 43 | 'summary' => $summary ?? Generator::UNDEFINED, 44 | 'security' => $security ?? Generator::UNDEFINED, 45 | 'servers' => $servers ?? Generator::UNDEFINED, 46 | 'tags' => $tags ?? Generator::UNDEFINED, 47 | 'callbacks' => $callbacks ?? Generator::UNDEFINED, 48 | 'deprecated' => $deprecated ?? Generator::UNDEFINED, 49 | 'x' => $x ?? Generator::UNDEFINED, 50 | 'attachables' => $attachables ?? Generator::UNDEFINED, 51 | 'value' => $this->combine($requestBody, $responses, $parameters, $externalDocs), 52 | ]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Attributes/Options.php: -------------------------------------------------------------------------------- 1 | $examples 17 | * @param array|JsonContent|XmlContent|Attachable|null $content 18 | * @param array|null $x 19 | * @param Attachable[]|null $attachables 20 | */ 21 | public function __construct( 22 | ?string $parameter = null, 23 | ?string $name = null, 24 | ?string $description = null, 25 | ?string $in = null, 26 | ?bool $required = null, 27 | ?bool $deprecated = null, 28 | ?bool $allowEmptyValue = null, 29 | string|object|null $ref = null, 30 | ?Schema $schema = null, 31 | mixed $example = Generator::UNDEFINED, 32 | ?array $examples = null, 33 | array|JsonContent|XmlContent|Attachable|null $content = null, 34 | ?string $style = null, 35 | ?bool $explode = null, 36 | ?bool $allowReserved = null, 37 | ?array $spaceDelimited = null, 38 | ?array $pipeDelimited = null, 39 | // annotation 40 | ?array $x = null, 41 | ?array $attachables = null 42 | ) { 43 | parent::__construct([ 44 | 'parameter' => $parameter ?? Generator::UNDEFINED, 45 | 'name' => $name ?? Generator::UNDEFINED, 46 | 'description' => $description ?? Generator::UNDEFINED, 47 | // next two are special as we override the default value for specific Parameter subclasses 48 | 'in' => $in ?? (Generator::isDefault($this->in) ? Generator::UNDEFINED : $this->in), 49 | 'required' => $required ?? (Generator::isDefault($this->required) ? Generator::UNDEFINED : $this->required), 50 | 'deprecated' => $deprecated ?? Generator::UNDEFINED, 51 | 'allowEmptyValue' => $allowEmptyValue ?? Generator::UNDEFINED, 52 | 'ref' => $ref ?? Generator::UNDEFINED, 53 | 'example' => $example, 54 | 'style' => $style ?? Generator::UNDEFINED, 55 | 'explode' => $explode ?? Generator::UNDEFINED, 56 | 'allowReserved' => $allowReserved ?? Generator::UNDEFINED, 57 | 'spaceDelimited' => $spaceDelimited ?? Generator::UNDEFINED, 58 | 'pipeDelimited' => $pipeDelimited ?? Generator::UNDEFINED, 59 | 'x' => $x ?? Generator::UNDEFINED, 60 | 'attachables' => $attachables ?? Generator::UNDEFINED, 61 | 'value' => $this->combine($schema, $examples, $content), 62 | ]); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Attributes/Patch.php: -------------------------------------------------------------------------------- 1 | |null $x 20 | * @param Attachable[]|null $attachables 21 | */ 22 | public function __construct( 23 | ?string $path = null, 24 | string|object|null $ref = null, 25 | ?string $summary = null, 26 | ?string $description = null, 27 | ?Get $get = null, 28 | ?Put $put = null, 29 | ?Post $post = null, 30 | ?Delete $delete = null, 31 | ?Options $options = null, 32 | ?Head $head = null, 33 | ?Patch $patch = null, 34 | ?Trace $trace = null, 35 | ?array $servers = null, 36 | ?array $parameters = null, 37 | // annotation 38 | ?array $x = null, 39 | ?array $attachables = null 40 | ) { 41 | parent::__construct([ 42 | 'path' => $path ?? Generator::UNDEFINED, 43 | 'ref' => $ref ?? Generator::UNDEFINED, 44 | 'summary' => $summary ?? Generator::UNDEFINED, 45 | 'description' => $description ?? Generator::UNDEFINED, 46 | 'x' => $x ?? Generator::UNDEFINED, 47 | 'attachables' => $attachables ?? Generator::UNDEFINED, 48 | 'value' => $this->combine($get, $put, $post, $delete, $options, $head, $patch, $trace, $servers, $parameters), 49 | ]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Attributes/PathParameter.php: -------------------------------------------------------------------------------- 1 | |MediaType|JsonContent|XmlContent|Attachable|null $content 18 | * @param array|null $x 19 | * @param Attachable[]|null $attachables 20 | */ 21 | public function __construct( 22 | string|object|null $ref = null, 23 | ?string $request = null, 24 | ?string $description = null, 25 | ?bool $required = null, 26 | array|MediaType|JsonContent|XmlContent|Attachable|null $content = null, 27 | // annotation 28 | ?array $x = null, 29 | ?array $attachables = null 30 | ) { 31 | parent::__construct([ 32 | 'ref' => $ref ?? Generator::UNDEFINED, 33 | 'request' => $request ?? Generator::UNDEFINED, 34 | 'description' => $description ?? Generator::UNDEFINED, 35 | 'required' => $required ?? Generator::UNDEFINED, 36 | 'x' => $x ?? Generator::UNDEFINED, 37 | 'attachables' => $attachables ?? Generator::UNDEFINED, 38 | 'value' => $this->combine($content), 39 | ]); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Attributes/Response.php: -------------------------------------------------------------------------------- 1 | $content 19 | * @param Link[] $links 20 | * @param array|null $x 21 | * @param Attachable[]|null $attachables 22 | */ 23 | public function __construct( 24 | string|object|null $ref = null, 25 | int|string|null $response = null, 26 | ?string $description = null, 27 | ?array $headers = null, 28 | MediaType|JsonContent|XmlContent|Attachable|array|null $content = null, 29 | ?array $links = null, 30 | // annotation 31 | ?array $x = null, 32 | ?array $attachables = null 33 | ) { 34 | parent::__construct([ 35 | 'ref' => $ref ?? Generator::UNDEFINED, 36 | 'response' => $response ?? Generator::UNDEFINED, 37 | 'description' => $description ?? Generator::UNDEFINED, 38 | 'x' => $x ?? Generator::UNDEFINED, 39 | 'attachables' => $attachables ?? Generator::UNDEFINED, 40 | 'value' => $this->combine($headers, $content, $links), 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Attributes/SecurityScheme.php: -------------------------------------------------------------------------------- 1 | |null $type 18 | * @param Flow[] $flows 19 | * @param array|null $x 20 | * @param Attachable[]|null $attachables 21 | */ 22 | public function __construct( 23 | string|object|null $ref = null, 24 | ?string $securityScheme = null, 25 | string|array|null $type = null, 26 | ?string $description = null, 27 | ?string $name = null, 28 | ?string $in = null, 29 | ?string $bearerFormat = null, 30 | ?string $scheme = null, 31 | ?string $openIdConnectUrl = null, 32 | ?array $flows = null, 33 | // annotation 34 | ?array $x = null, 35 | ?array $attachables = null 36 | ) { 37 | parent::__construct([ 38 | 'ref' => $ref ?? Generator::UNDEFINED, 39 | 'securityScheme' => $securityScheme ?? Generator::UNDEFINED, 40 | 'type' => $type ?? Generator::UNDEFINED, 41 | 'description' => $description ?? Generator::UNDEFINED, 42 | 'name' => $name ?? Generator::UNDEFINED, 43 | 'in' => $in ?? Generator::UNDEFINED, 44 | 'bearerFormat' => $bearerFormat ?? Generator::UNDEFINED, 45 | 'scheme' => $scheme ?? Generator::UNDEFINED, 46 | 'openIdConnectUrl' => $openIdConnectUrl ?? Generator::UNDEFINED, 47 | 'x' => $x ?? Generator::UNDEFINED, 48 | 'attachables' => $attachables ?? Generator::UNDEFINED, 49 | 'value' => $this->combine($flows), 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Attributes/Server.php: -------------------------------------------------------------------------------- 1 | |null $x 18 | * @param Attachable[]|null $attachables 19 | */ 20 | public function __construct( 21 | ?string $url = null, 22 | ?string $description = null, 23 | ?array $variables = null, 24 | // annotation 25 | ?array $x = null, 26 | ?array $attachables = null 27 | ) { 28 | parent::__construct([ 29 | 'url' => $url ?? Generator::UNDEFINED, 30 | 'description' => $description ?? Generator::UNDEFINED, 31 | 'x' => $x ?? Generator::UNDEFINED, 32 | 'attachables' => $attachables ?? Generator::UNDEFINED, 33 | 'value' => $this->combine($variables), 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Attributes/ServerVariable.php: -------------------------------------------------------------------------------- 1 | |class-string|null $enum 17 | * @param array|null $x 18 | * @param Attachable[]|null $attachables 19 | */ 20 | public function __construct( 21 | ?string $serverVariable = null, 22 | ?string $description = null, 23 | ?string $default = null, 24 | array|string|null $enum = null, 25 | ?array $variables = null, 26 | // annotation 27 | ?array $x = null, 28 | ?array $attachables = null 29 | ) { 30 | parent::__construct([ 31 | 'serverVariable' => $serverVariable ?? Generator::UNDEFINED, 32 | 'description' => $description ?? Generator::UNDEFINED, 33 | 'default' => $default ?? Generator::UNDEFINED, 34 | 'enum' => $enum ?? Generator::UNDEFINED, 35 | 'variables' => $variables ?? Generator::UNDEFINED, 36 | 'x' => $x ?? Generator::UNDEFINED, 37 | 'attachables' => $attachables ?? Generator::UNDEFINED, 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Attributes/Tag.php: -------------------------------------------------------------------------------- 1 | |null $x 17 | * @param Attachable[]|null $attachables 18 | */ 19 | public function __construct( 20 | ?string $name = null, 21 | ?string $description = null, 22 | ?ExternalDocumentation $externalDocs = null, 23 | // annotation 24 | ?array $x = null, 25 | ?array $attachables = null 26 | ) { 27 | parent::__construct([ 28 | 'name' => $name ?? Generator::UNDEFINED, 29 | 'description' => $description ?? Generator::UNDEFINED, 30 | 'x' => $x ?? Generator::UNDEFINED, 31 | 'attachables' => $attachables ?? Generator::UNDEFINED, 32 | 'value' => $this->combine($externalDocs), 33 | ]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Attributes/Trace.php: -------------------------------------------------------------------------------- 1 | |null $x 20 | * @param Attachable[]|null $attachables 21 | */ 22 | public function __construct( 23 | ?string $webhook = null, 24 | ?string $path = null, 25 | string|object|null $ref = null, 26 | ?string $summary = null, 27 | ?string $description = null, 28 | ?Get $get = null, 29 | ?Put $put = null, 30 | ?Post $post = null, 31 | ?Delete $delete = null, 32 | ?Options $options = null, 33 | ?Head $head = null, 34 | ?Patch $patch = null, 35 | ?Trace $trace = null, 36 | ?array $servers = null, 37 | ?array $parameters = null, 38 | // annotation 39 | ?array $x = null, 40 | ?array $attachables = null 41 | ) { 42 | parent::__construct([ 43 | 'webhook' => $webhook ?? Generator::UNDEFINED, 44 | 'path' => $path ?? Generator::UNDEFINED, 45 | 'ref' => $ref ?? Generator::UNDEFINED, 46 | 'summary' => $summary ?? Generator::UNDEFINED, 47 | 'description' => $description ?? Generator::UNDEFINED, 48 | 'x' => $x ?? Generator::UNDEFINED, 49 | 'attachables' => $attachables ?? Generator::UNDEFINED, 50 | 'value' => $this->combine($get, $put, $post, $delete, $options, $head, $patch, $trace, $servers, $parameters), 51 | ]); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Attributes/Xml.php: -------------------------------------------------------------------------------- 1 | |null $x 17 | * @param Attachable[]|null $attachables 18 | */ 19 | public function __construct( 20 | ?string $name = null, 21 | ?string $namespace = null, 22 | ?string $prefix = null, 23 | ?bool $attribute = null, 24 | ?bool $wrapped = null, 25 | // annotation 26 | ?array $x = null, 27 | ?array $attachables = null 28 | ) { 29 | parent::__construct([ 30 | 'name' => $name ?? Generator::UNDEFINED, 31 | 'namespace' => $namespace ?? Generator::UNDEFINED, 32 | 'prefix' => $prefix ?? Generator::UNDEFINED, 33 | 'attribute' => $attribute ?? Generator::UNDEFINED, 34 | 'wrapped' => $wrapped ?? Generator::UNDEFINED, 35 | 'x' => $x ?? Generator::UNDEFINED, 36 | 'attachables' => $attachables ?? Generator::UNDEFINED, 37 | ]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/GeneratorAwareInterface.php: -------------------------------------------------------------------------------- 1 | generator = $generator; 16 | 17 | return $this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Loggers/ConsoleLogger.php: -------------------------------------------------------------------------------- 1 | debug = $debug; 33 | } 34 | 35 | public function loggedMessageAboveNotice(): bool 36 | { 37 | return $this->loggedMessageAboveNotice; 38 | } 39 | 40 | /** 41 | * @param string $level 42 | * @param string|\Exception $message 43 | * @param array $context additional details; supports custom prefix and exception 44 | */ 45 | public function log($level, $message, array $context = []): void 46 | { 47 | $prefix = ''; 48 | $color = ''; 49 | // level adjustments 50 | switch ($level) { 51 | case LogLevel::DEBUG: 52 | if (!$this->debug) { 53 | return; 54 | } 55 | $prefix = 'Debug: '; 56 | // no break 57 | case LogLevel::WARNING: 58 | $prefix = $prefix ?: ($context['prefix'] ?? 'Warning: '); 59 | $color = static::COLOR_WARNING; 60 | break; 61 | case LogLevel::ERROR: 62 | $prefix = $context['prefix'] ?? 'Error: '; 63 | $color = static::COLOR_ERROR; 64 | break; 65 | } 66 | $stop = empty($color) ? '' : static::COLOR_STOP; 67 | 68 | if (!in_array($level, self::LOG_LEVELS_UP_TO_NOTICE, true)) { 69 | $this->loggedMessageAboveNotice = true; 70 | } 71 | 72 | /** @var ?\Exception $exception */ 73 | $exception = $context['exception'] ?? null; 74 | if ($message instanceof \Exception) { 75 | $exception = $message; 76 | $message = $exception->getMessage(); 77 | } 78 | 79 | $logLine = sprintf('%s%s%s%s', $color, $prefix, $message, $stop); 80 | error_log($logLine); 81 | 82 | if ($this->debug) { 83 | if ($exception) { 84 | error_log($exception->getTraceAsString()); 85 | } elseif ($logLine !== '' && $logLine !== '0') { 86 | $stack = explode(PHP_EOL, (new \Exception())->getTraceAsString()); 87 | // self 88 | array_shift($stack); 89 | // AbstractLogger 90 | array_shift($stack); 91 | foreach ($stack as $line) { 92 | error_log($line); 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Loggers/DefaultLogger.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $pipes = []; 15 | 16 | public function __construct(array $pipes = []) 17 | { 18 | $this->pipes = $pipes; 19 | } 20 | 21 | public function add(callable $pipe): Pipeline 22 | { 23 | $this->pipes[] = $pipe; 24 | 25 | return $this; 26 | } 27 | 28 | /** 29 | * @param callable|class-string|null $pipe 30 | */ 31 | public function remove($pipe = null, ?callable $matcher = null): Pipeline 32 | { 33 | if (!$pipe && !$matcher) { 34 | throw new OpenApiException('pipe or callable must not be empty'); 35 | } 36 | 37 | // allow matching on class name in $pipe in a string 38 | if (is_string($pipe) && !$matcher) { 39 | $pipeClass = $pipe; 40 | $matcher = function ($pipe) use ($pipeClass) { 41 | return !$pipe instanceof $pipeClass; 42 | }; 43 | } 44 | 45 | if ($matcher) { 46 | $tmp = []; 47 | foreach ($this->pipes as $pipe) { 48 | if ($matcher($pipe)) { 49 | $tmp[] = $pipe; 50 | } 51 | } 52 | 53 | $this->pipes = $tmp; 54 | } else { 55 | if (false === ($key = array_search($pipe, $this->pipes, true))) { 56 | return $this; 57 | } 58 | 59 | unset($this->pipes[$key]); 60 | 61 | $this->pipes = array_values($this->pipes); 62 | } 63 | 64 | return $this; 65 | } 66 | 67 | /** 68 | * @param callable|class-string $matcher used to determine the position to insert 69 | * either an int from a callable or, in the case of $matcher being 70 | * a class-string, the position before the first pipe of that class 71 | */ 72 | public function insert(callable $pipe, $matcher): Pipeline 73 | { 74 | if (is_string($matcher)) { 75 | $before = $matcher; 76 | $matcher = function (array $pipes) use ($before) { 77 | foreach ($pipes as $ii => $current) { 78 | if ($current instanceof $before) { 79 | return $ii; 80 | } 81 | } 82 | 83 | return null; 84 | }; 85 | } 86 | 87 | $index = $matcher($this->pipes); 88 | if (null === $index || $index < 0 || $index > count($this->pipes)) { 89 | throw new OpenApiException('Matcher result out of range'); 90 | } 91 | 92 | array_splice($this->pipes, $index, 0, [$pipe]); 93 | 94 | return $this; 95 | } 96 | 97 | public function walk(callable $walker): Pipeline 98 | { 99 | foreach ($this->pipes as $pipe) { 100 | $walker($pipe); 101 | } 102 | 103 | return $this; 104 | } 105 | 106 | /** 107 | * @param mixed $payload 108 | * 109 | * @return mixed 110 | */ 111 | public function process($payload) 112 | { 113 | foreach ($this->pipes as $pipe) { 114 | /** @deprecated null payload returned from pipe */ 115 | $payload = $pipe($payload) ?: $payload; 116 | } 117 | 118 | return $payload; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Processors/AugmentDiscriminators.php: -------------------------------------------------------------------------------- 1 | getAnnotationsOfType(OA\Discriminator::class); 22 | 23 | foreach ($discriminators as $discriminator) { 24 | if (!Generator::isDefault($discriminator->mapping)) { 25 | foreach ($discriminator->mapping as $value => $type) { 26 | if (is_string($type) && $typeSchema = $analysis->getSchemaForSource($type)) { 27 | $discriminator->mapping[$value] = OA\Components::ref($typeSchema); 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Processors/AugmentParameters.php: -------------------------------------------------------------------------------- 1 | augmentOperationParameters = $augmentOperationParameters; 28 | } 29 | 30 | public function isAugmentOperationParameters(): bool 31 | { 32 | return $this->augmentOperationParameters; 33 | } 34 | 35 | /** 36 | * If set to true try to find operation parameter descriptions in the operation docblock. 37 | */ 38 | public function setAugmentOperationParameters(bool $augmentOperationParameters): AugmentParameters 39 | { 40 | $this->augmentOperationParameters = $augmentOperationParameters; 41 | 42 | return $this; 43 | } 44 | 45 | public function __invoke(Analysis $analysis) 46 | { 47 | $this->augmentSharedParameters($analysis); 48 | if ($this->augmentOperationParameters) { 49 | $this->augmentOperationParameters($analysis); 50 | } 51 | } 52 | 53 | /** 54 | * Use the parameter->name as key field (parameter->parameter) when used as reusable component 55 | * (openapi->components->parameters). 56 | */ 57 | protected function augmentSharedParameters(Analysis $analysis): void 58 | { 59 | if (!Generator::isDefault($analysis->openapi->components) && !Generator::isDefault($analysis->openapi->components->parameters)) { 60 | $keys = []; 61 | $parametersWithoutKey = []; 62 | foreach ($analysis->openapi->components->parameters as $parameter) { 63 | if (!Generator::isDefault($parameter->parameter)) { 64 | $keys[$parameter->parameter] = $parameter; 65 | } else { 66 | $parametersWithoutKey[] = $parameter; 67 | } 68 | } 69 | foreach ($parametersWithoutKey as $parameter) { 70 | if (!Generator::isDefault($parameter->name) && empty($keys[$parameter->name])) { 71 | $parameter->parameter = $parameter->name; 72 | $keys[$parameter->parameter] = $parameter; 73 | } 74 | } 75 | } 76 | } 77 | 78 | protected function augmentOperationParameters(Analysis $analysis): void 79 | { 80 | /** @var OA\Operation[] $operations */ 81 | $operations = $analysis->getAnnotationsOfType(OA\Operation::class); 82 | 83 | foreach ($operations as $operation) { 84 | if (!Generator::isDefault($operation->parameters)) { 85 | $tags = []; 86 | $this->extractContent($operation->_context->comment, $tags); 87 | if (array_key_exists('param', $tags)) { 88 | foreach ($tags['param'] as $name => $details) { 89 | foreach ($operation->parameters as $parameter) { 90 | if ($parameter->name == $name) { 91 | if (Generator::isDefault($parameter->description) && $details['description']) { 92 | $parameter->description = $details['description']; 93 | } 94 | } 95 | 96 | if (!Generator::isDefault($parameter->schema)) { 97 | $this->mapNativeType($parameter->schema, $parameter->schema->type); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Processors/AugmentRefs.php: -------------------------------------------------------------------------------- 1 | resolveAllOfRefs($analysis); 20 | $this->resolveFQCNRefs($analysis); 21 | $this->removeDuplicateRefs($analysis); 22 | } 23 | 24 | /** 25 | * Update refs broken due to allOf augmenting. 26 | */ 27 | protected function resolveAllOfRefs(Analysis $analysis): void 28 | { 29 | /** @var OA\Schema[] $schemas */ 30 | $schemas = $analysis->getAnnotationsOfType(OA\Schema::class); 31 | 32 | // ref rewriting 33 | $updatedRefs = []; 34 | foreach ($schemas as $schema) { 35 | if (!Generator::isDefault($schema->allOf)) { 36 | // do we have to keep track of properties refs that need updating? 37 | foreach ($schema->allOf as $ii => $allOfSchema) { 38 | if (!Generator::isDefault($allOfSchema->properties)) { 39 | $updatedRefs[OA\Components::ref($schema->schema . '/properties', false)] = OA\Components::ref($schema->schema . '/allOf/' . $ii . '/properties', false); 40 | break; 41 | } 42 | } 43 | } 44 | } 45 | 46 | if ($updatedRefs) { 47 | foreach ($analysis->annotations as $annotation) { 48 | if (property_exists($annotation, 'ref') && !Generator::isDefault($annotation->ref) && $annotation->ref !== null) { 49 | foreach ($updatedRefs as $origRef => $updatedRef) { 50 | if (0 === strpos($annotation->ref, $origRef)) { 51 | $annotation->ref = str_replace($origRef, $updatedRef, $annotation->ref); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | protected function resolveFQCNRefs(Analysis $analysis): void 60 | { 61 | /** @var OA\AbstractAnnotation[] $annotations */ 62 | $annotations = $analysis->getAnnotationsOfType(OA\Components::componentTypes()); 63 | 64 | foreach ($annotations as $annotation) { 65 | if (property_exists($annotation, 'ref') && !Generator::isDefault($annotation->ref) && is_string($annotation->ref) && !$this->isRef($annotation->ref)) { 66 | // check if we can resolve the ref to a component 67 | $resolved = false; 68 | foreach (OA\Components::componentTypes() as $type) { 69 | if ($refSchema = $analysis->getAnnotationForSource($annotation->ref, $type)) { 70 | $resolved = true; 71 | $annotation->ref = OA\Components::ref($refSchema); 72 | } 73 | } 74 | if (!$resolved && ($refAnnotation = $analysis->getAnnotationForSource($annotation->ref, get_class($annotation)))) { 75 | $annotation->ref = OA\Components::ref($refAnnotation); 76 | } 77 | } 78 | } 79 | } 80 | 81 | protected function removeDuplicateRefs(Analysis $analysis): void 82 | { 83 | /** @var OA\Schema[] $schemas */ 84 | $schemas = $analysis->getAnnotationsOfType(OA\Schema::class); 85 | 86 | foreach ($schemas as $schema) { 87 | if (!Generator::isDefault($schema->allOf)) { 88 | $refs = []; 89 | $dupes = []; 90 | foreach ($schema->allOf as $ii => $allOfSchema) { 91 | if (!Generator::isDefault($allOfSchema->ref)) { 92 | if (in_array($allOfSchema->ref, $refs)) { 93 | $dupes[] = $allOfSchema->ref; 94 | $analysis->annotations->detach($allOfSchema); 95 | unset($schema->allOf[$ii]); 96 | continue; 97 | } 98 | $refs[] = $allOfSchema->ref; 99 | } 100 | } 101 | if ($dupes) { 102 | $schema->allOf = array_values($schema->allOf); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Processors/AugmentRequestBody.php: -------------------------------------------------------------------------------- 1 | $requests */ 21 | $requests = $analysis->getAnnotationsOfType(OA\RequestBody::class); 22 | 23 | $this->augmentRequestBody($requests); 24 | } 25 | 26 | /** 27 | * @param array $requests 28 | */ 29 | protected function augmentRequestBody(array $requests): void 30 | { 31 | foreach ($requests as $request) { 32 | if (!$request->isRoot(OA\RequestBody::class)) { 33 | continue; 34 | } 35 | if (Generator::isDefault($request->request)) { 36 | if ($request->_context->is('class')) { 37 | $request->request = $request->_context->class; 38 | } elseif ($request->_context->is('interface')) { 39 | $request->request = $request->_context->interface; 40 | } elseif ($request->_context->is('trait')) { 41 | $request->request = $request->_context->trait; 42 | } elseif ($request->_context->is('enum')) { 43 | $request->request = $request->_context->enum; 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Processors/AugmentTags.php: -------------------------------------------------------------------------------- 1 | tags list. 15 | */ 16 | class AugmentTags 17 | { 18 | /** @var array */ 19 | protected array $whitelist = []; 20 | 21 | public function __construct(array $whitelist = []) 22 | { 23 | $this->whitelist = $whitelist; 24 | } 25 | 26 | /** 27 | * Whitelist tags to keep even if not used. * may be used to keep all unused. 28 | */ 29 | public function setWhitelist(array $whitelist): AugmentTags 30 | { 31 | $this->whitelist = $whitelist; 32 | 33 | return $this; 34 | } 35 | 36 | public function __invoke(Analysis $analysis) 37 | { 38 | /** @var OA\Operation[] $operations */ 39 | $operations = $analysis->getAnnotationsOfType(OA\Operation::class); 40 | 41 | $usedTagNames = []; 42 | foreach ($operations as $operation) { 43 | if (!Generator::isDefault($operation->tags)) { 44 | $usedTagNames = array_merge($usedTagNames, $operation->tags); 45 | } 46 | } 47 | $usedTagNames = array_unique($usedTagNames); 48 | 49 | $declaredTags = []; 50 | if (!Generator::isDefault($analysis->openapi->tags)) { 51 | foreach ($analysis->openapi->tags as $tag) { 52 | $declaredTags[$tag->name] = $tag; 53 | } 54 | } 55 | if ($declaredTags) { 56 | // last one wins 57 | $analysis->openapi->tags = array_values($declaredTags); 58 | } 59 | 60 | // Add a tag for each tag that is used in operations but not declared in the global tags 61 | if ($usedTagNames) { 62 | $declatedTagNames = array_keys($declaredTags); 63 | foreach ($usedTagNames as $tagName) { 64 | if (!in_array($tagName, $declatedTagNames)) { 65 | $analysis->openapi->merge([new OA\Tag(['name' => $tagName, 'description' => $tagName])]); 66 | } 67 | } 68 | } 69 | 70 | $this->removeUnusedTags($usedTagNames, $declaredTags, $analysis); 71 | } 72 | 73 | private function removeUnusedTags(array $usedTagNames, array $declaredTags, Analysis $analysis) 74 | { 75 | if (in_array('*', $this->whitelist)) { 76 | return; 77 | } 78 | 79 | $tagsToKeep = array_merge($usedTagNames, $this->whitelist); 80 | foreach ($declaredTags as $tag) { 81 | if (!in_array($tag->name, $tagsToKeep)) { 82 | if (false !== $index = array_search($tag, $analysis->openapi->tags, true)) { 83 | $analysis->annotations->detach($tag); 84 | unset($analysis->openapi->tags[$index]); 85 | $analysis->openapi->tags = array_values($analysis->openapi->tags); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Processors/BuildPaths.php: -------------------------------------------------------------------------------- 1 | paths using the detected @OA\PathItem and @OA\Operation (@OA\Get, etc). 16 | */ 17 | class BuildPaths 18 | { 19 | public function __invoke(Analysis $analysis) 20 | { 21 | $paths = []; 22 | // Merge @OA\PathItems with the same path. 23 | if (!Generator::isDefault($analysis->openapi->paths)) { 24 | foreach ($analysis->openapi->paths as $annotation) { 25 | if (empty($annotation->path)) { 26 | $annotation->_context->logger->warning($annotation->identity() . ' is missing required property "path" in ' . $annotation->_context); 27 | } elseif (isset($paths[$annotation->path])) { 28 | $paths[$annotation->path]->mergeProperties($annotation); 29 | $analysis->annotations->detach($annotation); 30 | } else { 31 | $paths[$annotation->path] = $annotation; 32 | } 33 | } 34 | } 35 | 36 | /** @var OA\Operation[] $operations */ 37 | $operations = $analysis->unmerged()->getAnnotationsOfType(OA\Operation::class); 38 | 39 | // Merge @OA\Operations into existing @OA\PathItems or create a new one. 40 | foreach ($operations as $operation) { 41 | if ($operation->path) { 42 | if (empty($paths[$operation->path])) { 43 | $paths[$operation->path] = $pathItem = new OA\PathItem( 44 | [ 45 | 'path' => $operation->path, 46 | '_context' => new Context(['generated' => true], $operation->_context), 47 | ] 48 | ); 49 | $analysis->addAnnotation($pathItem, $pathItem->_context); 50 | } 51 | if ($paths[$operation->path]->merge([$operation])) { 52 | $operation->_context->logger->warning('Unable to merge ' . $operation->identity() . ' in ' . $operation->_context); 53 | } 54 | } 55 | } 56 | if ($paths) { 57 | $analysis->openapi->paths = array_values($paths); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Processors/CleanUnmerged.php: -------------------------------------------------------------------------------- 1 | split(); 17 | $merged = $split->merged->annotations; 18 | $unmerged = $split->unmerged->annotations; 19 | 20 | /** @var OA\AbstractAnnotation $annotation */ 21 | foreach ($analysis->annotations as $annotation) { 22 | if (property_exists($annotation, '_unmerged')) { 23 | foreach ($annotation->_unmerged as $ii => $item) { 24 | if ($merged->contains($item)) { 25 | unset($annotation->_unmerged[$ii]); // Property was merged 26 | } 27 | } 28 | } 29 | } 30 | $analysis->openapi->_unmerged = []; 31 | foreach ($unmerged as $annotation) { 32 | $analysis->openapi->_unmerged[] = $annotation; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Processors/CleanUnusedComponents.php: -------------------------------------------------------------------------------- 1 | Components and removed unused schemas. 15 | */ 16 | class CleanUnusedComponents 17 | { 18 | use Concerns\AnnotationTrait; 19 | 20 | protected bool $enabled; 21 | 22 | public function __construct(bool $enabled = false) 23 | { 24 | $this->enabled = $enabled; 25 | } 26 | 27 | public function isEnabled(): bool 28 | { 29 | return $this->enabled; 30 | } 31 | 32 | /** 33 | * Enables/disables the CleanUnusedComponents processor. 34 | */ 35 | public function setEnabled(bool $enabled): CleanUnusedComponents 36 | { 37 | $this->enabled = $enabled; 38 | 39 | return $this; 40 | } 41 | 42 | public function __invoke(Analysis $analysis) 43 | { 44 | if (!$this->enabled || Generator::isDefault($analysis->openapi->components)) { 45 | return; 46 | } 47 | 48 | $analysis->annotations = $this->collectAnnotations($analysis->annotations); 49 | 50 | // allow multiple runs to catch nested dependencies 51 | for ($ii = 0; $ii < 10; ++$ii) { 52 | if (!$this->cleanup($analysis)) { 53 | break; 54 | } 55 | } 56 | } 57 | 58 | protected function cleanup(Analysis $analysis): bool 59 | { 60 | $usedRefs = []; 61 | foreach ($analysis->annotations as $annotation) { 62 | if (property_exists($annotation, 'ref') && !Generator::isDefault($annotation->ref) && $annotation->ref !== null) { 63 | $usedRefs[$annotation->ref] = $annotation->ref; 64 | } 65 | 66 | foreach (['allOf', 'anyOf', 'oneOf'] as $sub) { 67 | if (property_exists($annotation, $sub) && !Generator::isDefault($annotation->{$sub})) { 68 | foreach ($annotation->{$sub} as $subElem) { 69 | if (is_object($subElem) && property_exists($subElem, 'ref') && !Generator::isDefault($subElem->ref) && $subElem->ref !== null) { 70 | $usedRefs[$subElem->ref] = $subElem->ref; 71 | } 72 | } 73 | } 74 | } 75 | 76 | if ($annotation instanceof OA\OpenApi || $annotation instanceof OA\Operation) { 77 | if (!Generator::isDefault($annotation->security)) { 78 | foreach ($annotation->security as $security) { 79 | foreach (array_keys($security) as $securityName) { 80 | $ref = OA\Components::COMPONENTS_PREFIX . 'securitySchemes/' . $securityName; 81 | $usedRefs[$ref] = $ref; 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | $unusedRefs = []; 89 | foreach (OA\Components::$_nested as $nested) { 90 | if (2 == count($nested)) { 91 | // $nested[1] is the name of the property that holds the component name 92 | [$componentType, $nameProperty] = $nested; 93 | if (!Generator::isDefault($analysis->openapi->components->{$componentType})) { 94 | foreach ($analysis->openapi->components->{$componentType} as $component) { 95 | $ref = OA\Components::ref($component); 96 | if (!in_array($ref, $usedRefs)) { 97 | $unusedRefs[$ref] = [$ref, $nameProperty]; 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | // remove unused 105 | foreach ($unusedRefs as $refDetails) { 106 | [$ref, $nameProperty] = $refDetails; 107 | [$hash, $components, $componentType, $name] = explode('/', $ref); 108 | foreach ($analysis->openapi->components->{$componentType} as $ii => $component) { 109 | if ($component->{$nameProperty} == $name) { 110 | $annotation = $analysis->openapi->components->{$componentType}[$ii]; 111 | $this->removeAnnotation($analysis->annotations, $annotation); 112 | unset($analysis->openapi->components->{$componentType}[$ii]); 113 | 114 | if (!$analysis->openapi->components->{$componentType}) { 115 | $analysis->openapi->components->{$componentType} = Generator::UNDEFINED; 116 | } 117 | } 118 | } 119 | } 120 | 121 | return 0 != count($unusedRefs); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Processors/Concerns/AnnotationTrait.php: -------------------------------------------------------------------------------- 1 | traverseAnnotations($root, function ($item) use (&$storage) { 23 | if ($item instanceof OA\AbstractAnnotation && !$storage->contains($item)) { 24 | $storage->attach($item); 25 | } 26 | }); 27 | 28 | return $storage; 29 | } 30 | 31 | /** 32 | * Remove all annotations that are part of the $annotation tree. 33 | */ 34 | public function removeAnnotation(iterable $root, OA\AbstractAnnotation $annotation, bool $recurse = true): void 35 | { 36 | $remove = $this->collectAnnotations($annotation); 37 | $this->traverseAnnotations($root, function ($item) use ($remove) { 38 | if ($item instanceof \SplObjectStorage) { 39 | foreach ($remove as $annotation) { 40 | $item->detach($annotation); 41 | } 42 | } 43 | }, $recurse); 44 | } 45 | 46 | /** 47 | * @param string|array|iterable|OA\AbstractAnnotation $root 48 | */ 49 | public function traverseAnnotations($root, callable $callable, bool $recurse = true): void 50 | { 51 | $callable($root); 52 | 53 | if (is_iterable($root) && $recurse) { 54 | foreach ($root as $value) { 55 | $this->traverseAnnotations($value, $callable, $recurse); 56 | } 57 | } elseif ($root instanceof OA\AbstractAnnotation) { 58 | foreach (array_merge($root::$_nested, ['allOf', 'anyOf', 'oneOf', 'callbacks']) as $properties) { 59 | foreach ((array) $properties as $property) { 60 | if (isset($root->{$property})) { 61 | $this->traverseAnnotations($root->{$property}, $callable, $recurse); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Processors/Concerns/MergePropertiesTrait.php: -------------------------------------------------------------------------------- 1 | update all $ref that might reference a property merged. 22 | */ 23 | trait MergePropertiesTrait 24 | { 25 | protected function inheritFrom(Analysis $analysis, OA\Schema $schema, OA\Schema $from, string $refPath, Context $context): void 26 | { 27 | if (Generator::isDefault($schema->allOf)) { 28 | $schema->allOf = []; 29 | } 30 | // merging other properties into allOf is done in the AugmentSchemas processor 31 | $schema->allOf[] = $refSchema = new OA\Schema([ 32 | 'ref' => OA\Components::ref($refPath), 33 | '_context' => new Context(['generated' => true], $context), 34 | ]); 35 | $analysis->addAnnotation($refSchema, $refSchema->_context); 36 | } 37 | 38 | protected function mergeProperties(OA\Schema $schema, array $from, array &$existing): void 39 | { 40 | foreach ($from['properties'] as $context) { 41 | if (is_iterable($context->annotations)) { 42 | foreach ($context->annotations as $annotation) { 43 | if ($annotation instanceof OA\Property && !in_array($annotation->_context->property, $existing, true)) { 44 | $existing[] = $annotation->_context->property; 45 | $schema->merge([$annotation], true); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | protected function mergeMethods(OA\Schema $schema, array $from, array &$existing): void 53 | { 54 | foreach ($from['methods'] as $context) { 55 | if (is_iterable($context->annotations)) { 56 | foreach ($context->annotations as $annotation) { 57 | if ($annotation instanceof OA\Property && !in_array($annotation->_context->property, $existing, true)) { 58 | $existing[] = $annotation->_context->property; 59 | $schema->merge([$annotation], true); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Processors/Concerns/RefTrait.php: -------------------------------------------------------------------------------- 1 | fullyQualifiedName($name)); 16 | 17 | return ltrim($fqn, '\\'); 18 | } 19 | 20 | protected function isRef(?string $ref): bool 21 | { 22 | return $ref && 0 === strpos($ref, '#/'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Processors/Concerns/TypesTrait.php: -------------------------------------------------------------------------------- 1 | 'array', 16 | 'byte' => ['string', 'byte'], 17 | 'boolean' => 'boolean', 18 | 'bool' => 'boolean', 19 | 'int' => 'integer', 20 | 'integer' => 'integer', 21 | 'long' => ['integer', 'long'], 22 | 'float' => ['number', 'float'], 23 | 'double' => ['number', 'double'], 24 | 'string' => 'string', 25 | 'date' => ['string', 'date'], 26 | 'datetime' => ['string', 'date-time'], 27 | '\\datetime' => ['string', 'date-time'], 28 | 'datetimeimmutable' => ['string', 'date-time'], 29 | '\\datetimeimmutable' => ['string', 'date-time'], 30 | 'datetimeinterface' => ['string', 'date-time'], 31 | '\\datetimeinterface' => ['string', 'date-time'], 32 | 'number' => 'number', 33 | 'object' => 'object', 34 | ]; 35 | 36 | public function mapNativeType(OA\Schema $schema, string $type): bool 37 | { 38 | if (!array_key_exists($type, self::$NATIVE_TYPE_MAP)) { 39 | return false; 40 | } 41 | 42 | $type = self::$NATIVE_TYPE_MAP[$type]; 43 | if (is_array($type)) { 44 | if (Generator::isDefault($schema->format)) { 45 | $schema->format = $type[1]; 46 | } 47 | $type = $type[0]; 48 | } 49 | 50 | $schema->type = $type; 51 | 52 | return true; 53 | } 54 | 55 | public function native2spec(string $type): string 56 | { 57 | $mapped = array_key_exists($type, self::$NATIVE_TYPE_MAP) ? self::$NATIVE_TYPE_MAP[$type] : $type; 58 | 59 | return is_array($mapped) ? $mapped[0] : $mapped; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Processors/DocBlockDescriptions.php: -------------------------------------------------------------------------------- 1 | null, for example: @Annotation(description=null), if you don't want the annotation to have a description. 18 | */ 19 | class DocBlockDescriptions 20 | { 21 | use Concerns\DocblockTrait; 22 | 23 | public function __invoke(Analysis $analysis) 24 | { 25 | /** @var OA\AbstractAnnotation $annotation */ 26 | foreach ($analysis->annotations as $annotation) { 27 | if (property_exists($annotation, '_context') === false) { 28 | // only annotations with context 29 | continue; 30 | } 31 | 32 | if (!$this->isRoot($annotation)) { 33 | // only top-level annotations 34 | continue; 35 | } 36 | 37 | $hasSummary = property_exists($annotation, 'summary'); 38 | $hasDescription = property_exists($annotation, 'description'); 39 | if (!$hasSummary && !$hasDescription) { 40 | continue; 41 | } 42 | 43 | if ($hasSummary && $hasDescription) { 44 | $this->summaryAndDescription($annotation); 45 | } elseif ($hasDescription) { 46 | $this->description($annotation); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * @param OA\Operation|OA\Property|OA\Parameter|OA\Schema $annotation 53 | */ 54 | protected function description(OA\AbstractAnnotation $annotation): void 55 | { 56 | if (!Generator::isDefault($annotation->description)) { 57 | if ($annotation->description === null) { 58 | $annotation->description = Generator::UNDEFINED; 59 | } 60 | 61 | return; 62 | } 63 | 64 | $annotation->description = $this->extractContent($annotation->_context->comment); 65 | } 66 | 67 | /** 68 | * @param OA\Operation|OA\Property|OA\Parameter|OA\Schema $annotation 69 | */ 70 | protected function summaryAndDescription(OA\AbstractAnnotation $annotation): void 71 | { 72 | $ignoreSummary = !Generator::isDefault($annotation->summary); 73 | $ignoreDescription = !Generator::isDefault($annotation->description); 74 | if ($annotation->summary === null) { 75 | $ignoreSummary = true; 76 | $annotation->summary = Generator::UNDEFINED; 77 | } 78 | if ($annotation->description === null) { 79 | $annotation->description = Generator::UNDEFINED; 80 | $ignoreDescription = true; 81 | } 82 | if ($ignoreSummary && $ignoreDescription) { 83 | return; 84 | } 85 | if ($ignoreSummary) { 86 | $annotation->description = $this->extractContent($annotation->_context->comment); 87 | } elseif ($ignoreDescription) { 88 | $annotation->summary = $this->extractContent($annotation->_context->comment); 89 | } else { 90 | $annotation->summary = $this->extractSummary($annotation->_context->comment); 91 | $annotation->description = $this->extractDescription($annotation->_context->comment); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Processors/ExpandClasses.php: -------------------------------------------------------------------------------- 1 | inherit from the ancestor if it has a schema (allOf) and stop. 17 | * - else 18 | * => merge ancestor properties into the schema. 19 | */ 20 | class ExpandClasses 21 | { 22 | use Concerns\MergePropertiesTrait; 23 | 24 | public function __invoke(Analysis $analysis) 25 | { 26 | /** @var OA\Schema[] $schemas */ 27 | $schemas = $analysis->getAnnotationsOfType(OA\Schema::class, true); 28 | 29 | foreach ($schemas as $schema) { 30 | if ($schema->_context->is('class')) { 31 | $ancestors = $analysis->getSuperClasses($schema->_context->fullyQualifiedName($schema->_context->class)); 32 | $existing = []; 33 | foreach ($ancestors as $ancestor) { 34 | $ancestorSchema = $analysis->getSchemaForSource($ancestor['context']->fullyQualifiedName($ancestor['class'])); 35 | if ($ancestorSchema) { 36 | $refPath = Generator::isDefault($ancestorSchema->schema) ? $ancestor['class'] : $ancestorSchema->schema; 37 | $this->inheritFrom($analysis, $schema, $ancestorSchema, $refPath, $ancestor['context']); 38 | 39 | // one ancestor is enough 40 | break; 41 | } else { 42 | $this->mergeMethods($schema, $ancestor, $existing); 43 | $this->mergeProperties($schema, $ancestor, $existing); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Processors/ExpandEnums.php: -------------------------------------------------------------------------------- 1 | schema, enum and type. 18 | */ 19 | class ExpandEnums 20 | { 21 | use Concerns\TypesTrait; 22 | 23 | protected ?string $enumNames; 24 | 25 | public function __construct(?string $enumNames = null) 26 | { 27 | $this->enumNames = $enumNames; 28 | } 29 | 30 | public function getEnumNames(): ?string 31 | { 32 | return $this->enumNames; 33 | } 34 | 35 | /** 36 | * Specifies the name of the extension variable where backed enum names will be stored. 37 | * Set to null to avoid writing backed enum names. 38 | * 39 | * Example: 40 | * ->setEnumNames('enumNames') yields: 41 | * ```yaml 42 | * x-enumNames: 43 | * - NAME1 44 | * - NAME2 45 | * ``` 46 | */ 47 | public function setEnumNames(?string $enumNames = null): void 48 | { 49 | $this->enumNames = $enumNames; 50 | } 51 | 52 | public function __invoke(Analysis $analysis) 53 | { 54 | if (!class_exists('\\ReflectionEnum')) { 55 | return; 56 | } 57 | 58 | $this->expandContextEnum($analysis); 59 | $this->expandSchemaEnum($analysis); 60 | } 61 | 62 | protected function expandContextEnum(Analysis $analysis): void 63 | { 64 | /** @var OA\Schema[] $schemas */ 65 | $schemas = $analysis->getAnnotationsOfType(OA\Schema::class, true); 66 | 67 | foreach ($schemas as $schema) { 68 | if ($schema->_context->is('enum')) { 69 | $re = new \ReflectionEnum($schema->_context->fullyQualifiedName($schema->_context->enum)); 70 | $schema->schema = Generator::isDefault($schema->schema) ? $re->getShortName() : $schema->schema; 71 | 72 | $schemaType = $schema->type; 73 | $enumType = null; 74 | if ($re->isBacked()) { 75 | $backingType = $re->getBackingType(); 76 | if ($backingType instanceof \ReflectionNamedType) { 77 | $enumType = $backingType->getName(); 78 | } 79 | } 80 | 81 | // no (or invalid) schema type means name 82 | $useName = Generator::isDefault($schemaType) || ($enumType && $this->native2spec($enumType) != $schemaType); 83 | 84 | $schema->enum = array_map(function ($case) use ($useName) { 85 | return ($useName || !($case instanceof \ReflectionEnumBackedCase)) ? $case->name : $case->getBackingValue(); 86 | }, $re->getCases()); 87 | 88 | if ($this->enumNames !== null && !$useName) { 89 | $schemaX = Generator::isDefault($schema->x) ? [] : $schema->x; 90 | $schemaX[$this->enumNames] = array_map(function ($case) { 91 | return $case->name; 92 | }, $re->getCases()); 93 | 94 | $schema->x = $schemaX; 95 | } 96 | 97 | $schema->type = $useName ? 'string' : $enumType; 98 | 99 | $this->mapNativeType($schema, $schemaType); 100 | } 101 | } 102 | } 103 | 104 | protected function expandSchemaEnum(Analysis $analysis): void 105 | { 106 | /** @var OA\Schema[] $schemas */ 107 | $schemas = $analysis->getAnnotationsOfType([OA\Schema::class, OA\ServerVariable::class]); 108 | 109 | foreach ($schemas as $schema) { 110 | if (Generator::isDefault($schema->enum)) { 111 | continue; 112 | } 113 | 114 | if (is_string($schema->enum)) { 115 | // might be enum class-string 116 | if (is_a($schema->enum, \UnitEnum::class, true)) { 117 | $cases = $schema->enum::cases(); 118 | } else { 119 | throw new OpenApiException("Unexpected enum value, requires specifying the Enum class string: $schema->enum"); 120 | } 121 | } else { 122 | // might be an array of \UnitEnum::class, string, int, etc... 123 | assert(is_array($schema->enum)); 124 | 125 | $cases = []; 126 | 127 | // transform \UnitEnum into individual cases 128 | /** @var string|class-string<\UnitEnum> $enum */ 129 | foreach ($schema->enum as $enum) { 130 | if (is_string($enum) && function_exists('enum_exists') && enum_exists($enum)) { 131 | foreach ($enum::cases() as $case) { 132 | $cases[] = $case; 133 | } 134 | } else { 135 | $cases[] = $enum; 136 | } 137 | } 138 | } 139 | 140 | $enums = []; 141 | foreach ($cases as $enum) { 142 | $enums[] = is_a($enum, \UnitEnum::class) ? $enum->value ?? $enum->name : $enum; 143 | } 144 | 145 | $schema->enum = $enums; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Processors/ExpandInterfaces.php: -------------------------------------------------------------------------------- 1 | getAnnotationsOfType(OA\Schema::class, true); 26 | 27 | foreach ($schemas as $schema) { 28 | if ($schema->_context->is('class')) { 29 | $className = $schema->_context->fullyQualifiedName($schema->_context->class); 30 | $interfaces = $analysis->getInterfacesOfClass($className, true); 31 | 32 | if (class_exists($className) && ($parent = get_parent_class($className)) && ($inherited = array_keys(class_implements($parent)))) { 33 | // strip interfaces we inherit from ancestor 34 | foreach (array_keys($interfaces) as $interface) { 35 | if (in_array(ltrim($interface, '\\'), $inherited)) { 36 | unset($interfaces[$interface]); 37 | } 38 | } 39 | } 40 | 41 | $existing = []; 42 | foreach ($interfaces as $interface) { 43 | $interfaceName = $interface['context']->fullyQualifiedName($interface['interface']); 44 | $interfaceSchema = $analysis->getSchemaForSource($interfaceName); 45 | if ($interfaceSchema) { 46 | $refPath = Generator::isDefault($interfaceSchema->schema) ? $interface['interface'] : $interfaceSchema->schema; 47 | $this->inheritFrom($analysis, $schema, $interfaceSchema, $refPath, $interface['context']); 48 | } else { 49 | $this->mergeMethods($schema, $interface, $existing); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Processors/ExpandTraits.php: -------------------------------------------------------------------------------- 1 | getAnnotationsOfType(OA\Schema::class, true); 26 | 27 | // do regular trait inheritance / merge 28 | foreach ($schemas as $schema) { 29 | if ($schema->_context->is('trait')) { 30 | $traits = $analysis->getTraitsOfClass($schema->_context->fullyQualifiedName($schema->_context->trait), true); 31 | $existing = []; 32 | foreach ($traits as $trait) { 33 | $traitSchema = $analysis->getSchemaForSource($trait['context']->fullyQualifiedName($trait['trait'])); 34 | if ($traitSchema) { 35 | $refPath = Generator::isDefault($traitSchema->schema) ? $trait['trait'] : $traitSchema->schema; 36 | $this->inheritFrom($analysis, $schema, $traitSchema, $refPath, $trait['context']); 37 | } else { 38 | $this->mergeMethods($schema, $trait, $existing); 39 | $this->mergeProperties($schema, $trait, $existing); 40 | } 41 | } 42 | } 43 | } 44 | 45 | foreach ($schemas as $schema) { 46 | if ($schema->_context->is('class') && !$schema->_context->is('generated')) { 47 | // look at class traits 48 | $traits = $analysis->getTraitsOfClass($schema->_context->fullyQualifiedName($schema->_context->class), true); 49 | $existing = []; 50 | foreach ($traits as $trait) { 51 | $traitSchema = $analysis->getSchemaForSource($trait['context']->fullyQualifiedName($trait['trait'])); 52 | if ($traitSchema) { 53 | $refPath = Generator::isDefault($traitSchema->schema) ? $trait['trait'] : $traitSchema->schema; 54 | $this->inheritFrom($analysis, $schema, $traitSchema, $refPath, $trait['context']); 55 | } else { 56 | $this->mergeMethods($schema, $trait, $existing); 57 | $this->mergeProperties($schema, $trait, $existing); 58 | } 59 | } 60 | 61 | // also merge ancestor traits of non schema parents 62 | $ancestors = $analysis->getSuperClasses($schema->_context->fullyQualifiedName($schema->_context->class)); 63 | $existing = []; 64 | foreach ($ancestors as $ancestor) { 65 | $ancestorSchema = $analysis->getSchemaForSource($ancestor['context']->fullyQualifiedName($ancestor['class'])); 66 | if ($ancestorSchema) { 67 | // stop here as we inherit everything above 68 | break; 69 | } else { 70 | $traits = $analysis->getTraitsOfClass($schema->_context->fullyQualifiedName($ancestor['class']), true); 71 | foreach ($traits as $trait) { 72 | $this->mergeMethods($schema, $trait, $existing); 73 | $this->mergeProperties($schema, $trait, $existing); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Processors/MergeIntoComponents.php: -------------------------------------------------------------------------------- 1 | @OA\Schemas. 16 | */ 17 | class MergeIntoComponents 18 | { 19 | public function __invoke(Analysis $analysis) 20 | { 21 | $components = $analysis->openapi->components; 22 | if (Generator::isDefault($components)) { 23 | $components = new OA\Components(['_context' => new Context(['generated' => true], $analysis->context)]); 24 | } 25 | 26 | /** @var OA\AbstractAnnotation $annotation */ 27 | foreach ($analysis->annotations as $annotation) { 28 | if ($annotation instanceof OA\AbstractAnnotation 29 | && in_array(OA\Components::class, $annotation::$_parents) 30 | && false === $annotation->_context->is('nested')) { 31 | // A top level annotation. 32 | $components->merge([$annotation], true); 33 | $analysis->openapi->components = $components; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Processors/MergeIntoOpenApi.php: -------------------------------------------------------------------------------- 1 | @OA\OpenApi annotations into one. 16 | */ 17 | class MergeIntoOpenApi 18 | { 19 | public function __invoke(Analysis $analysis) 20 | { 21 | // Auto-create the OpenApi annotation. 22 | if (!$analysis->openapi) { 23 | $context = new Context([], $analysis->context); 24 | $analysis->addAnnotation(new OA\OpenApi(['_context' => $context]), $context); 25 | } 26 | $openapi = $analysis->openapi; 27 | $openapi->_analysis = $analysis; 28 | 29 | // Merge annotations into the target openapi 30 | $merge = []; 31 | /** @var OA\AbstractAnnotation $annotation */ 32 | foreach ($analysis->annotations as $annotation) { 33 | if ($annotation === $openapi) { 34 | continue; 35 | } 36 | if ($annotation instanceof OA\OpenApi) { 37 | $paths = $annotation->paths; 38 | unset($annotation->paths); 39 | $openapi->mergeProperties($annotation); 40 | if (!Generator::isDefault($paths)) { 41 | foreach ($paths as $path) { 42 | if (Generator::isDefault($openapi->paths)) { 43 | $openapi->paths = []; 44 | } 45 | $openapi->paths[] = $path; 46 | } 47 | } 48 | } elseif ( 49 | $annotation instanceof OA\AbstractAnnotation 50 | && in_array(OA\OpenApi::class, $annotation::$_parents) 51 | && false === $annotation->_context->is('nested')) { 52 | // A top level annotation. 53 | $merge[] = $annotation; 54 | } 55 | } 56 | $openapi->merge($merge, true); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Processors/MergeJsonContent.php: -------------------------------------------------------------------------------- 1 | getAnnotationsOfType(OA\JsonContent::class); 23 | 24 | foreach ($annotations as $jsonContent) { 25 | $parent = $jsonContent->_context->nested; 26 | if (!($parent instanceof OA\Response) && !($parent instanceof OA\RequestBody) && !($parent instanceof OA\Parameter)) { 27 | if ($parent) { 28 | $jsonContent->_context->logger->warning('Unexpected ' . $jsonContent->identity() . ' in ' . $parent->identity() . ' in ' . $parent->_context); 29 | } else { 30 | $jsonContent->_context->logger->warning('Unexpected ' . $jsonContent->identity() . ' must be nested'); 31 | } 32 | continue; 33 | } 34 | if (Generator::isDefault($parent->content)) { 35 | $parent->content = []; 36 | } 37 | $parent->content['application/json'] = $mediaType = new OA\MediaType([ 38 | 'schema' => $jsonContent, 39 | 'example' => $jsonContent->example, 40 | 'examples' => $jsonContent->examples, 41 | '_context' => new Context(['generated' => true], $jsonContent->_context), 42 | ]); 43 | $analysis->addAnnotation($mediaType, $mediaType->_context); 44 | if (!$parent instanceof OA\Parameter) { 45 | $parent->content['application/json']->mediaType = 'application/json'; 46 | } 47 | $jsonContent->example = Generator::UNDEFINED; 48 | $jsonContent->examples = Generator::UNDEFINED; 49 | 50 | $index = array_search($jsonContent, $parent->_unmerged, true); 51 | if ($index !== false) { 52 | array_splice($parent->_unmerged, $index, 1); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Processors/MergeXmlContent.php: -------------------------------------------------------------------------------- 1 | getAnnotationsOfType(OA\XmlContent::class); 23 | 24 | foreach ($annotations as $xmlContent) { 25 | $parent = $xmlContent->_context->nested; 26 | if (!($parent instanceof OA\Response) && !($parent instanceof OA\RequestBody) && !($parent instanceof OA\Parameter)) { 27 | if ($parent) { 28 | $xmlContent->_context->logger->warning('Unexpected ' . $xmlContent->identity() . ' in ' . $parent->identity() . ' in ' . $parent->_context); 29 | } else { 30 | $xmlContent->_context->logger->warning('Unexpected ' . $xmlContent->identity() . ' must be nested'); 31 | } 32 | continue; 33 | } 34 | if (Generator::isDefault($parent->content)) { 35 | $parent->content = []; 36 | } 37 | $parent->content['application/xml'] = $mediaType = new OA\MediaType([ 38 | 'schema' => $xmlContent, 39 | 'example' => $xmlContent->example, 40 | 'examples' => $xmlContent->examples, 41 | '_context' => new Context(['generated' => true], $xmlContent->_context), 42 | ]); 43 | $analysis->addAnnotation($mediaType, $mediaType->_context); 44 | if (!$parent instanceof OA\Parameter) { 45 | $parent->content['application/xml']->mediaType = 'application/xml'; 46 | } 47 | $xmlContent->example = Generator::UNDEFINED; 48 | $xmlContent->examples = Generator::UNDEFINED; 49 | 50 | $index = array_search($xmlContent, $parent->_unmerged, true); 51 | if ($index !== false) { 52 | array_splice($parent->_unmerged, $index, 1); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Processors/OperationId.php: -------------------------------------------------------------------------------- 1 | hash = $hash; 23 | } 24 | 25 | public function isHash(): bool 26 | { 27 | return $this->hash; 28 | } 29 | 30 | /** 31 | * If set to true generate ids (md5) instead of clear text operation ids. 32 | */ 33 | public function setHash(bool $hash): OperationId 34 | { 35 | $this->hash = $hash; 36 | 37 | return $this; 38 | } 39 | 40 | public function __invoke(Analysis $analysis) 41 | { 42 | $allOperations = $analysis->getAnnotationsOfType(OA\Operation::class); 43 | 44 | /** @var OA\Operation $operation */ 45 | foreach ($allOperations as $operation) { 46 | if (null === $operation->operationId) { 47 | $operation->operationId = Generator::UNDEFINED; 48 | } 49 | 50 | if (!Generator::isDefault($operation->operationId)) { 51 | continue; 52 | } 53 | 54 | $context = $operation->_context; 55 | if ($context) { 56 | $source = $context->class ?? $context->interface ?? $context->trait; 57 | $operationId = null; 58 | if ($source) { 59 | $method = $context->method ? ('::' . $context->method) : ''; 60 | $operationId = $context->namespace ? $context->namespace . '\\' . $source . $method : $source . $method; 61 | } elseif ($context->method) { 62 | $operationId = $context->method; 63 | } 64 | 65 | if ($operationId) { 66 | $operationId = strtoupper($operation->method) . '::' . $operation->path . '::' . $operationId; 67 | $operation->operationId = $this->hash ? md5($operationId) : $operationId; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Processors/PathFilter.php: -------------------------------------------------------------------------------- 1 | tags or paths filters are set, no filtering is performed. 17 | * 18 | * All filter (regular) expressions must be enclosed within delimiter characters as they are used as-is. 19 | */ 20 | class PathFilter 21 | { 22 | use AnnotationTrait; 23 | 24 | protected array $tags; 25 | 26 | protected array $paths; 27 | 28 | protected bool $recurseCleanup; 29 | 30 | public function __construct(array $tags = [], array $paths = [], bool $recurseCleanup = false) 31 | { 32 | $this->tags = $tags; 33 | $this->paths = $paths; 34 | $this->recurseCleanup = $recurseCleanup; 35 | } 36 | 37 | public function getTags(): array 38 | { 39 | return $this->tags; 40 | } 41 | 42 | /** 43 | * A list of regular expressions to match tags to include. 44 | * 45 | * @param array $tags 46 | */ 47 | public function setTags(array $tags): PathFilter 48 | { 49 | $this->tags = $tags; 50 | 51 | return $this; 52 | } 53 | 54 | public function getPaths(): array 55 | { 56 | return $this->paths; 57 | } 58 | 59 | /** 60 | * A list of regular expressions to match paths to include. 61 | * 62 | * @param array $paths 63 | */ 64 | public function setPaths(array $paths): PathFilter 65 | { 66 | $this->paths = $paths; 67 | 68 | return $this; 69 | } 70 | 71 | public function isRecurseCleanup(): bool 72 | { 73 | return $this->recurseCleanup; 74 | } 75 | 76 | /** 77 | * Flag to do a recursive cleanup of unused paths and their nested annotations. 78 | */ 79 | public function setRecurseCleanup(bool $recurseCleanup): void 80 | { 81 | $this->recurseCleanup = $recurseCleanup; 82 | } 83 | 84 | public function __invoke(Analysis $analysis) 85 | { 86 | if (($this->tags || $this->paths) && !Generator::isDefault($analysis->openapi->paths)) { 87 | $filtered = []; 88 | foreach ($analysis->openapi->paths as $pathItem) { 89 | $matched = null; 90 | foreach ($this->tags as $pattern) { 91 | foreach ($pathItem->operations() as $operation) { 92 | if (!Generator::isDefault($operation->tags)) { 93 | foreach ($operation->tags as $tag) { 94 | if (preg_match($pattern, $tag)) { 95 | $matched = $pathItem; 96 | break 3; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | foreach ($this->paths as $pattern) { 104 | if (preg_match($pattern, $pathItem->path)) { 105 | $matched = $pathItem; 106 | break; 107 | } 108 | } 109 | 110 | if ($matched) { 111 | $filtered[] = $matched; 112 | } else { 113 | $this->removeAnnotation($analysis->annotations, $pathItem, $this->recurseCleanup); 114 | } 115 | } 116 | 117 | $analysis->openapi->paths = $filtered; 118 | } 119 | } 120 | } 121 | --------------------------------------------------------------------------------