diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1d2aaba..8c139c7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,6 @@ version: 2 updates: -- package-ecosystem: composer +- package-ecosystem: "github-actions" directory: "/" schedule: - interval: daily - time: "17:00" - open-pull-requests-limit: 10 + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7087b53..a5ba8d2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,15 +3,21 @@ name: build on: push: branches: - - master + - main pull_request: branches: - - master + - main jobs: call-laravel: - uses: DerManoMann/openapi-router/.github/workflows/laravel.yml@master + uses: ./.github/workflows/laravel.yml + with: + php-versions: '8.1,8.2,8.3,8.4' call-lumen: - uses: DerManoMann/openapi-router/.github/workflows/lumen.yml@master + uses: ./.github/workflows/lumen.yml + with: + php-versions: '8.1,8.2,8.3,8.4' call-slim: - uses: DerManoMann/openapi-router/.github/workflows/slim.yml@master + uses: ./.github/workflows/slim.yml + with: + php-versions: '8.1,8.2,8.3,8.4' diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 84b8512..9ef6404 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -3,22 +3,22 @@ name: code-style on: push: branches: - - master + - main pull_request: branches: - - master + - main jobs: php-cs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Checkout repository - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.3' - uses: ramsey/composer-install@v1 with: diff --git a/.github/workflows/laravel.yml b/.github/workflows/laravel.yml index d068dcd..b0cf3fa 100644 --- a/.github/workflows/laravel.yml +++ b/.github/workflows/laravel.yml @@ -2,6 +2,10 @@ name: laravel on: workflow_call: + inputs: + php-versions: + required: true + type: string jobs: test: @@ -13,26 +17,19 @@ jobs: fail-fast: true matrix: operating-system: [ ubuntu-latest ] - php: [ '7.2', '7.3', '7.4', '8.0', '8.1' ] - laravel: [ '7.0', '8.0', '9.0' ] + php: ${{ fromJson(format('[{0}]', inputs.php-versions)) }} + laravel: [ '10.0', '11.0' ] dependencies: [ 'lowest', ' highest' ] exclude: - - php: '7.2' - laravel: '9.0' - - php: '7.2' - laravel: '8.0' - - php: '7.3' - laravel: '9.0' - - php: '7.4' - laravel: '9.0' - php: '8.1' - laravel: '7.0' - - dependencies: 'lowest' + laravel: '11.0' + - laravel: '11.0' + dependencies: 'lowest' name: PHP ${{ matrix.php }} / Laravel ${{ matrix.laravel }} on ${{ matrix.operating-system }} with ${{ matrix.dependencies }} dependencies steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Checkout repository - name: Setup PHP diff --git a/.github/workflows/lumen.yml b/.github/workflows/lumen.yml index 3907592..58becd4 100644 --- a/.github/workflows/lumen.yml +++ b/.github/workflows/lumen.yml @@ -2,6 +2,10 @@ name: lumen on: workflow_call: + inputs: + php-versions: + required: true + type: string jobs: test: @@ -13,27 +17,14 @@ jobs: fail-fast: true matrix: operating-system: [ ubuntu-latest ] - php: [ '7.2', '7.3', '7.4', '8.0', '8.1' ] - lumen: [ '8.0', '9.0' ] + php: ${{ fromJson(format('[{0}]', inputs.php-versions)) }} + lumen: [ '10.0' ] dependencies: [ 'highest' ] - exclude: - - php: '7.2' - lumen: '9.0' - - php: '7.2' - lumen: '8.0' - - php: '7.3' - lumen: '9.0' - - php: '7.4' - lumen: '9.0' - - php: '8.1' - lumen: '8.0' - - php: '8.1' - dependencies: 'lowest' name: PHP ${{ matrix.php }} / Lumen ${{ matrix.lumen }} on ${{ matrix.operating-system }} with ${{ matrix.dependencies }} dependencies steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Checkout repository - name: Setup PHP diff --git a/.github/workflows/security-checks.yml b/.github/workflows/security-checks.yml index 173d87b..9db4aa6 100644 --- a/.github/workflows/security-checks.yml +++ b/.github/workflows/security-checks.yml @@ -3,28 +3,25 @@ name: security-checks on: push: branches: - - master + - main pull_request: branches: - - master + - main jobs: security-checker: runs-on: ${{ matrix.operating-system }} - env: - COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GITHUB_TOKEN }}"}}' - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} strategy: fail-fast: true matrix: operating-system: [ ubuntu-latest ] - php: [ '8.0', '8.1' ] + php: [ '8.1', '8.2', '8.3', '8.4' ] dependencies: [ 'highest' ] name: PHP ${{ matrix.php }} on ${{ matrix.operating-system }} with ${{ matrix.dependencies }} dependencies steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Checkout repository - name: Setup PHP @@ -33,15 +30,15 @@ jobs: php-version: ${{ matrix.php }} - name: Composer install - uses: ramsey/composer-install@v1 + uses: ramsey/composer-install@v3 with: dependency-versions: ${{ matrix.dependencies }} composer-options: ${{ matrix.composer-options }} --no-dev - name: Cache security checker dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.symfony/cache key: security-checker-db - - uses: symfonycorp/security-checker-action@v2 + - uses: symfonycorp/security-checker-action@v5 diff --git a/.github/workflows/slim.yml b/.github/workflows/slim.yml index 02484a6..d0bfc2b 100644 --- a/.github/workflows/slim.yml +++ b/.github/workflows/slim.yml @@ -2,6 +2,10 @@ name: slim on: workflow_call: + inputs: + php-versions: + required: true + type: string jobs: test: @@ -13,17 +17,14 @@ jobs: fail-fast: true matrix: operating-system: [ ubuntu-latest ] - php: [ '8.0', '8.1' ] + php: ${{ fromJson(format('[{0}]', inputs.php-versions)) }} slim: [ '4.0' ] dependencies: [ 'lowest', 'highest' ] - exclude: - - php: '8.1' - dependencies: 'lowest' name: PHP ${{ matrix.php }} / Slim ${{ matrix.slim }} on ${{ matrix.operating-system }} with ${{ matrix.dependencies }} dependencies steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Checkout repository - name: Setup PHP diff --git a/README.md b/README.md index 79f08c3..99f8174 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,16 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ## Introduction -Allows to (re-)use [Swagger-PHP](https://github.com/zircote/swagger-php) annotations to configure routes in the -following frameworks: +Allows to (re-)use [Swagger-PHP](https://github.com/zircote/swagger-php) attributes (docblock annotations are deprecated), +to configure routes in the following frameworks: + * [Laravel](https://github.com/laravel/laravel) * [Lumen](https://github.com/laravel/lumen) * [Slim](https://github.com/slimphp/Slim) ## Requirements -* [PHP 7.2 or higher](http://www.php.net/) - depending on framework version. +* [PHP 8.1 or higher](http://www.php.net/) - depending on framework version. ## Installation @@ -34,7 +35,30 @@ After that all required classes should be availabe in your project to add routin ## Basic usage -Example using the `Slim` framework adapter and standard [OpenApi annotations](https://github.com/zircote/swagger-php/tree/master/src/Annotations) only. +Example using the `Slim` framework adapter and standard [OpenApi attributes](https://zircote.github.io/swagger-php/guide/attributes) only. + +**Controller** +```php +write('Get me'); + } +} +``` **index.php** ```php @@ -53,33 +77,8 @@ $app = new App(); $app->run(); ``` -**Controller** -```php -write('Get me'); - } -} -``` - ## Documentation * [Configuration](docs/Configuration.md) -* [Annotation extensions](docs/AnnotationExtensions.md) ## License diff --git a/TODO b/TODO deleted file mode 100644 index 9c36cb0..0000000 --- a/TODO +++ /dev/null @@ -1,4 +0,0 @@ -* Add support for: - * scheme - * parameter requirements (regex, etc) -* Wrap as service provider / middleware? diff --git a/composer.json b/composer.json index b447386..81ae2b4 100644 --- a/composer.json +++ b/composer.json @@ -35,24 +35,27 @@ }, "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-main": "3.x-dev" } }, "require": { - "php": ">=7.2", - "doctrine/annotations": "^1.13", - "psr/simple-cache": "^1.0 || ^2.0", - "zircote/swagger-php": "^4.2.3" + "php": ">=8.1", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", + "radebatz/openapi-extras": "^2.1", + "slim/slim": "^4.14", + "zircote/swagger-php": "^4.11.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", + "doctrine/annotations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", "nyholm/psr7": "^1.4", "nyholm/psr7-server": "^1.0", - "phpunit/phpunit": ">=8.0", - "symfony/cache": "^5.0 || ^6.0", - "symfony/psr-http-message-bridge": "^2.1" + "phpunit/phpunit": "^9.0 || ^10.5", + "symfony/cache": "^6.0 || ^7.0", + "symfony/psr-http-message-bridge": "^6.0 || ^7.0" }, "suggest": { - "radebatz/openapi-verifier": "Allows your PHPUnit tests to validate your controller response against the OpenAPI annotations." + "radebatz/openapi-verifier": "Allows your PHPUnit tests to validate your controller response against your OpenAPI spec." } } diff --git a/docs/AnnotationExtensions.md b/docs/AnnotationExtensions.md deleted file mode 100644 index 652847a..0000000 --- a/docs/AnnotationExtensions.md +++ /dev/null @@ -1,125 +0,0 @@ -# Annotation Extensions - -## Default Annotations -By default the router expects only standard [OpenApi annotations](https://github.com/zircote/swagger-php/tree/master/src/Annotations). - -Theses annotations have only limited build in support for advanced routing features. If you feel like -sticking to those the only way to add more options is to use the -[vendor extension](https://swagger.io/specification/#vendorExtensions) system. - -Vendor extensions are OpenApi properties starting with `x-`. Annotations supports this via the `x` annotation property. - -**Vendor extension example:** -```php - ... - - /** - * @OA\Get( - * path="/login", - * x={ - * "name": "login", - * "middleware": {"auth", "verified"} - * }, - * @OA\Response(response="200", description="All good") - * ) - */ -``` - -The example showcases the two vendor extensions that the router supports: -* **name** - - The name property is used to bind a (unique) name to each route which later can be used to lookup - the route (for example to generate a url). - - name binding is supported by all adapters. - -* **middleware** - - [Middleware](https://www.php-fig.org/psr/psr-15/) is a concept that only some frameworks support. In those cases - one or more middleware can be attached to a route as shown above. - - Middleware binding is supported by all adapters. - -As an alternative to using the x-name property it is also possible to use the standard `operationId` property to configure -a route name. - -**NOTE:** It is worth noticing that by default this property is set to `[Controller class]::[method name]` by the swagger-php -library. If you do not wish to use `operationId` it is recommended to disable using it as name value for route binding -(see the global `OPTION_OA_OPERATION_ID_AS_NAME` config option) - -## Extended Annotations -### Operations -As an alternative to the above syntax the openapi-router project provides its own (extended) versions of annotations for -all operations (`'get', 'post', 'put', 'patch', 'delete', 'options', 'head'`). - -These are registered with a namespace alias of `@OSX`. - -**Extended annotation example:** -```php - ... - - /** - * @OAX\Get( - * path="/foo", - * operationId="foo", - * @OA\Response(response="200", description="All good") - * ) - */ -``` - -### `Controller` Annotation -openapi-router also provides a `Controller` annotation that can be used to: -* apply a path prefix to all controller routes -* configure middlewares shared by all controller routes -* configure responses shared by all controller routes - -This annotation can be used with both standard and extended operation annotations. - -**Controller annotation example:** -```php -... - -/** - * @OAX\Controller( - * prefix="api/v1", - * middleware={"auth"}, - * @OA\Response(response=401, description="Not Authenticated") - * ) - */ -class UserController extends Controller -{ - /** - * @OA\Get( - * path="user/{id}/delete", - * operationId="transfer", - * middleware={"role:admin"}, - * @OA\Response(response="200", description="All good") - * ) - */ - public static function delete($request, $response, $id) - { - // delete user - } -``` - -## Attributes -As of PHP 8.1 swagger-php and openapi-router also allow to use PHP attributes instead of docblock annotations. -Names and features are the same with one exception - for middleware there is a new attribute `Middleware` which avoid having to -use the included customized swagger-php operation annotations. - -Here is an example taken from the test suite: -```php - -use Radebatz\OpenApi\Routing\Annotations as OAX; - - class AttributeController - { - #[OA\Get(path: '/prefixed', x: ['name' => 'attributes'])] - #[OA\Response(response: 200, description: 'All good')] - #[OAX\Middleware([BMiddleware::class])] - public function prefixed() - { - return FakeResponse::create('Get fooya'); - } - } -``` diff --git a/docs/Configuration.md b/docs/Configuration.md index d5a1a82..21f677a 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -4,7 +4,7 @@ The `OpenApiRouter` class takes an array (map) as optional third constructor argument which allows to customise its behaviour. -All option names (keys) are defined as class constants. +All option names (keys) are defined as class constants in `Radebatz\OpenApi\Routing\OpenApiRouter`. **`OPTION_RELOAD`** --- @@ -12,6 +12,8 @@ Enforces loading of route annotations on each request. Typically you want this turned off on production. Requires a cache confgured (annotation caching) or caching support implemented by the used adapter. +**Note**: When using a framework it is recommended to rely on the framework caching rather than using the (simple) build in cache. + Default: `true` **`OPTION_CACHE`** @@ -26,21 +28,24 @@ Default: `null` --- Controls whether to inject a default `@OA\Info` instance while scanning. -This can be useful if your top level OpenApi annotation is inside the scanned folder hierarchy. +This can be useful for testing or small projects. -Default: `true` +Default: `false` **`OPTION_OA_OPERATION_ID_AS_NAME`** --- -Controls whether to default the custom (x-) name property to the `operationId`. +Controls whether to use the configured `operationId` as the route name. If disabled the adapter will look for a vendor property +`x-name` on the operation (`Get`, `Post`, etc.) attribute. + +Allows to set the route name via the standard `operationId` rather than the vendor `x-name`. -Allows to set the route name via the standard `operationId` rather than `x-name`. -By default the `operationId` is populated with the controller (class/method) for the route. +**Note**: The default for `operationId` in `swagger-php` is to generate an operationId and hash it if it is explicitely set. generated Default: `true` ### Example use ```php + - + tests diff --git a/src/Annotations/Controller.php b/src/Annotations/Controller.php deleted file mode 100644 index 6b6b222..0000000 --- a/src/Annotations/Controller.php +++ /dev/null @@ -1,38 +0,0 @@ - ['responses', 'response'], - Attachable::class => ['attachables'], - ]; -} diff --git a/src/Annotations/Delete.php b/src/Annotations/Delete.php deleted file mode 100644 index 0f6247c..0000000 --- a/src/Annotations/Delete.php +++ /dev/null @@ -1,15 +0,0 @@ -names = $names; - } -} diff --git a/src/Annotations/MiddlewareProperty.php b/src/Annotations/MiddlewareProperty.php deleted file mode 100644 index 162db9c..0000000 --- a/src/Annotations/MiddlewareProperty.php +++ /dev/null @@ -1,15 +0,0 @@ - ['responses', 'response'], - Attachable::class => ['attachables'], - ]; - - /** - * @param array|null $responses - * @param array|null $attachables - */ - public function __construct( - ?string $prefix = null, - ?array $responses = null, - // annotation - ?array $x = null, - ?array $attachables = null - ) { - parent::__construct([ - 'prefix' => $prefix ?? Generator::UNDEFINED, - 'x' => $x ?? Generator::UNDEFINED, - 'value' => $this->combine($responses, $attachables), - ]); - } -} diff --git a/src/Attributes/Middleware.php b/src/Attributes/Middleware.php deleted file mode 100644 index e8508cc..0000000 --- a/src/Attributes/Middleware.php +++ /dev/null @@ -1,37 +0,0 @@ -names = $names; - } -} diff --git a/src/OpenApiRouter.php b/src/OpenApiRouter.php index efd7f5f..5a45447 100644 --- a/src/OpenApiRouter.php +++ b/src/OpenApiRouter.php @@ -2,24 +2,17 @@ namespace Radebatz\OpenApi\Routing; -use OpenApi\Analysers\AttributeAnnotationFactory; -use OpenApi\Analysers\DocBlockAnnotationFactory; -use OpenApi\Analysers\ReflectionAnalyser; use OpenApi\Analysis; use OpenApi\Annotations\Info; use OpenApi\Annotations\OpenApi; use OpenApi\Annotations\Operation; use OpenApi\Annotations\Parameter; -use OpenApi\Annotations\PathItem; use OpenApi\Context; use OpenApi\Generator; -use OpenApi\Processors\BuildPaths; +use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; -use Radebatz\OpenApi\Routing\Annotations as OAX; -use Radebatz\OpenApi\Routing\Annotations\Middleware; -use Radebatz\OpenApi\Routing\Annotations\MiddlewareProperty; -use Radebatz\OpenApi\Routing\Processors\ControllerCleanup; -use Radebatz\OpenApi\Routing\Processors\MergeController; +use Radebatz\OpenApi\Extras\OpenApiBuilder; +use Radebatz\OpenApi\Extras\Annotations\Middleware; use Symfony\Component\Finder\Finder; /** @@ -53,7 +46,7 @@ public function __construct($sources, RoutingAdapterInterface $routingAdapter, a $this->options = $options + [ self::OPTION_RELOAD => true, self::OPTION_CACHE => null, - self::OPTION_OA_INFO_INJECT => true, + self::OPTION_OA_INFO_INJECT => false, self::OPTION_OA_OPERATION_ID_AS_NAME => true, ]; } @@ -118,12 +111,6 @@ protected function registerOpenApi(OpenApi $openapi) } $middleware = []; - $uses = array_flip(class_uses($operation)); - if (array_key_exists(MiddlewareProperty::class, $uses)) { - if (!Generator::isDefault($operation->middleware) && is_array($operation->middleware)) { - $middleware = array_merge($middleware, $operation->middleware); - } - } if (!Generator::isDefault($operation->attachables)) { foreach ($operation->attachables as $attachable) { if ($attachable instanceof Middleware) { @@ -187,7 +174,7 @@ protected function parameterMetadata($parameters): array $metadata[$name]['type'] = $schema->type; if (!Generator::isDefault($pattern = $schema->pattern)) { $metadata[$name]['type'] = 'regex'; - $metadata[$name]['pattern'] = $schema->pattern; + $metadata[$name]['pattern'] = $pattern; } break; case 'integer': @@ -200,65 +187,20 @@ protected function parameterMetadata($parameters): array return $metadata; } - public function scan(): OpenApi + public function scan(?LoggerInterface $logger = null): OpenApi { - $generator = $this->generator(); + $generator = (new OpenApiBuilder())->build($logger); + // provide default @OA\Info in case we need to do some scanning $analysis = $generator->withContext(function (Generator $generator, Analysis $analysis, Context $context) { if ($this->options[self::OPTION_OA_INFO_INJECT]) { - $analysis->addAnnotation(new Info(['title' => 'Test', 'version' => '1.0']), $context); + $analysis->addAnnotation(new Info(['title' => 'OpenApi', 'version' => '1.0']), $context); } return $analysis; }); return $generator - ->setAnalyser(new ReflectionAnalyser([new DocBlockAnnotationFactory(), new AttributeAnnotationFactory()])) ->generate($this->sources, $analysis); } - - /** - * Set up Generator. - * - * Registers our custom annotations under the `oax` namespace alias. - */ - public function generator(): Generator - { - $operations = [ - OAX\Get::class => 'get', - OAX\Post::class => 'post', - OAX\Put::class => 'put', - OAX\Patch::class => 'patch', - OAX\Delete::class => 'delete', - OAX\Options::class => 'options', - OAX\Head::class => 'head', - OAX\Trace::class => 'trace', - ]; - foreach ($operations as $class => $operation) { - PathItem::$_nested[$class] = $operation; - $class::$_blacklist[] = 'middleware'; - } - - $routingNamespace = 'Radebatz\\OpenApi\\Routing\\Annotations'; - $generator = (new Generator()) - ->addNamespace($routingNamespace . '\\') - ->addAlias('oax', $routingNamespace) - ->addProcessor(new ControllerCleanup()); - - $processors = $generator->getProcessors(); - $insertMergeController = function (array $processors) { - $tmp = []; - foreach ($processors as $processor) { - if (get_class($processor) == BuildPaths::class) { - $tmp[] = new MergeController(); - } - $tmp[] = $processor; - } - - return $tmp; - }; - $generator->setProcessors($insertMergeController($processors)); - - return $generator; - } } diff --git a/src/Processors/ControllerCleanup.php b/src/Processors/ControllerCleanup.php deleted file mode 100644 index 8e3658c..0000000 --- a/src/Processors/ControllerCleanup.php +++ /dev/null @@ -1,37 +0,0 @@ -getAnnotationsOfType(Controller::class); - foreach ($controllers as $controller) { - $this->clearMerged($analysis, $controller); - $this->clearMerged($analysis, $controller->responses); - } - } - - protected function clearMerged(Analysis $analysis, $annotations) - { - if (Generator::isDefault($annotations)) { - return; - } - - $annotations = is_array($annotations) ? $annotations : [$annotations]; - - foreach ($annotations as $annotation) { - if (false !== ($offset = array_search($annotation, $analysis->openapi->_unmerged))) { - unset($analysis->openapi->_unmerged[$offset]); - } - } - } -} diff --git a/src/Processors/MergeController.php b/src/Processors/MergeController.php deleted file mode 100644 index 27842a9..0000000 --- a/src/Processors/MergeController.php +++ /dev/null @@ -1,94 +0,0 @@ -getAnnotationsOfType(Controller::class); - /** @var Operation[] $operations */ - $operations = $analysis->getAnnotationsOfType(Operation::class); - - foreach ($controllers as $controller) { - if ($this->needsProcessing($controller)) { - foreach ($operations as $operation) { - if ($this->contextMatch($operation, $controller->_context)) { - // update path - if (!Generator::isDefault($controller->prefix)) { - $path = $controller->prefix . '/' . $operation->path; - $operation->path = str_replace('//', '/', $path); - } - - if (!Generator::isDefault($controller->middleware) || !Generator::isDefault($controller->attachables)) { - $middleware = !Generator::isDefault($controller->middleware) ? $controller->middleware : []; - if (!Generator::isDefault($controller->attachables)) { - foreach ($controller->attachables as $attachable) { - if ($attachable instanceof AnnotationMiddleware || $attachable instanceof AttributeMiddleware) { - $middleware = array_merge($middleware, $attachable->names); - } - } - } - if (!Generator::isDefault($operation->attachables)) { - foreach ($operation->attachables as $attachable) { - if ($attachable instanceof AnnotationMiddleware || $attachable instanceof AttributeMiddleware) { - $middleware = array_merge($middleware, $attachable->names); - } - } - } - $middleware = array_unique($middleware); - - $uses = array_flip(class_uses($operation)); - if (array_key_exists(MiddlewareProperty::class, $uses)) { - $operation->middleware = !Generator::isDefault($operation->middleware) ? $operation->middleware : []; - $operation->middleware = array_merge($operation->middleware, $middleware); - } else { - // add as X property - $operation->x = !Generator::isDefault($operation->x) ? $operation->x : []; - $operation->x[RoutingAdapterInterface::X_MIDDLEWARE] = - array_key_exists(RoutingAdapterInterface::X_MIDDLEWARE, $operation->x) - ? $operation->x[RoutingAdapterInterface::X_MIDDLEWARE] - : []; - $operation->x[RoutingAdapterInterface::X_MIDDLEWARE] = - array_merge($operation->x[RoutingAdapterInterface::X_MIDDLEWARE], $middleware); - } - } - - if (!Generator::isDefault($controller->responses)) { - $operation->merge($controller->responses, true); - } - } - } - } - } - } - - protected function needsProcessing(Controller $controller) - { - return !Generator::isDefault($controller->prefix) - || !Generator::isDefault($controller->middleware) - || !Generator::isDefault($controller->responses) - || !Generator::isDefault($controller->attachables); - } - - protected function contextMatch(AbstractAnnotation $annotation, Context $context) - { - return $annotation->_context->namespace === $context->namespace - && $annotation->_context->class == $context->class; - } -} diff --git a/src/Processors/VendorPropertyValidation.php b/src/Processors/VendorPropertyValidation.php index 0086d72..feb6659 100644 --- a/src/Processors/VendorPropertyValidation.php +++ b/src/Processors/VendorPropertyValidation.php @@ -5,8 +5,7 @@ use OpenApi\Analysis; use OpenApi\Annotations\Operation; use OpenApi\Generator; -use Radebatz\OpenApi\Routing\Annotations\Middleware; -use Radebatz\OpenApi\Routing\Annotations\MiddlewareProperty; +use Radebatz\OpenApi\Extras\Annotations\Middleware; use Radebatz\OpenApi\Routing\RoutingAdapterInterface; /** @@ -42,12 +41,6 @@ public function __invoke(Analysis $analysis) $this->validateUniqueName($operation->operationId); } - $uses = array_flip(class_uses_recursive($operation)); - if (array_key_exists(MiddlewareProperty::class, $uses)) { - if (!Generator::isDefault($operation->middleware)) { - $this->validateVendorProperty(RoutingAdapterInterface::X_MIDDLEWARE, $operation->middleware); - } - } if (!Generator::isDefault($operation->attachables)) { foreach ($operation->attachables as $attachable) { if ($attachable instanceof Middleware) { diff --git a/tests/Fixtures/Controllers/AttributeController.php b/tests/Fixtures/Controllers/AttributeController.php index 4caaa61..36c8125 100644 --- a/tests/Fixtures/Controllers/AttributeController.php +++ b/tests/Fixtures/Controllers/AttributeController.php @@ -2,19 +2,19 @@ namespace Radebatz\OpenApi\Routing\Tests\Fixtures\Controllers; -use OpenApi\Attributes as OA; -use Radebatz\OpenApi\Routing\Attributes as OAX; +use OpenApi\Attributes as OAT; +use Radebatz\OpenApi\Extras\Attributes as OAX; use Radebatz\OpenApi\Routing\Tests\Fixtures\Middleware\BarMiddleware; use Radebatz\OpenApi\Routing\Tests\Fixtures\Middleware\FooMiddleware; if (\PHP_VERSION_ID >= 80100) { #[OAX\Controller(prefix: '/attributes')] - #[OA\Response(response: 403, description: 'Not allowed')] + #[OAT\Response(response: 403, description: 'Not allowed')] #[OAX\Middleware([FooMiddleware::class])] class AttributeController { - #[OA\Get(path: '/prefixed', x: ['name' => 'attributes'])] - #[OA\Response(response: 200, description: 'All good')] + #[OAT\Get(path: '/prefixed', x: ['name' => 'attributes'])] + #[OAT\Response(response: 200, description: 'All good')] #[OAX\Middleware([BarMiddleware::class])] public function prefixed() { diff --git a/tests/Fixtures/Controllers/InvokeController.php b/tests/Fixtures/Controllers/InvokeController.php index 842b4e9..470a07e 100644 --- a/tests/Fixtures/Controllers/InvokeController.php +++ b/tests/Fixtures/Controllers/InvokeController.php @@ -2,9 +2,12 @@ namespace Radebatz\OpenApi\Routing\Tests\Fixtures\Controllers; +use Radebatz\OpenApi\Extras\Annotations as OAX; + /** * @OAX\Controller( * prefix="/foo", + * * @OA\Response(response="401", description="Unauthorized") * ) */ @@ -16,6 +19,7 @@ class InvokeController * x={ * "name": "invoke" * }, + * * @OA\Response(response="200", description="All good") * ) */ diff --git a/tests/Fixtures/Controllers/MiddlewareController.php b/tests/Fixtures/Controllers/MiddlewareController.php index e99eb6e..2061960 100644 --- a/tests/Fixtures/Controllers/MiddlewareController.php +++ b/tests/Fixtures/Controllers/MiddlewareController.php @@ -2,6 +2,8 @@ namespace Radebatz\OpenApi\Routing\Tests\Fixtures\Controllers; +use OpenApi\Annotations as OA; + class MiddlewareController { /** @@ -11,6 +13,7 @@ class MiddlewareController * "name": "mw", * "middleware"={"Radebatz\OpenApi\Routing\Tests\Fixtures\Middleware\FooMiddleware", "Radebatz\OpenApi\Routing\Tests\Fixtures\Middleware\BarMiddleware"} * }, + * * @OA\Response(response="200", description="All good") * ) */ diff --git a/tests/Fixtures/Controllers/NamedRouteController.php b/tests/Fixtures/Controllers/NamedRouteController.php index 5f7c3b5..ce251f9 100644 --- a/tests/Fixtures/Controllers/NamedRouteController.php +++ b/tests/Fixtures/Controllers/NamedRouteController.php @@ -2,6 +2,8 @@ namespace Radebatz\OpenApi\Routing\Tests\Fixtures\Controllers; +use OpenApi\Annotations as OA; + class NamedRouteController { /** @@ -10,6 +12,7 @@ class NamedRouteController * x={ * "name": "getya" * }, + * * @OA\Response(response="200", description="All good") * ) */ @@ -24,6 +27,7 @@ public function getya() * x={ * "name": "static_getya" * }, + * * @OA\Response(response="200", description="All good") * ) */ diff --git a/tests/Fixtures/Controllers/ParametersController.php b/tests/Fixtures/Controllers/ParametersController.php index a43e25e..44c708e 100644 --- a/tests/Fixtures/Controllers/ParametersController.php +++ b/tests/Fixtures/Controllers/ParametersController.php @@ -2,6 +2,8 @@ namespace Radebatz\OpenApi\Routing\Tests\Fixtures\Controllers; +use OpenApi\Annotations as OA; + class ParametersController { /** @@ -10,15 +12,18 @@ class ParametersController * x={ * "name": "hey" * }, + * * @OA\Parameter( * name="name", * in="path", * required=true, * description="The name", + * * @OA\Schema( * type="string" * ) * ), + * * @OA\Response(response="200", description="All good") * ) */ @@ -33,15 +38,18 @@ public function hey($name) * x={ * "name": "oi" * }, + * * @OA\Parameter( * name="name", * in="path", * required=false, * description="The name", + * * @OA\Schema( * type="string" * ) * ), + * * @OA\Response(response="200", description="All good") * ) */ @@ -56,16 +64,19 @@ public function oi($name = 'you') * x={ * "name": "id" * }, + * * @OA\Parameter( * name="id", * in="path", * required=true, * description="The id", + * * @OA\Schema( * type="integer", * format="int32" * ) * ), + * * @OA\Response(response="200", description="All good") * ) */ @@ -80,16 +91,19 @@ public function id($id) * x={ * "name": "hid" * }, + * * @OA\Parameter( * name="hid", * in="path", * required=true, * description="The hid", + * * @OA\Schema( * type="string", * pattern="[0-9a-f]+" * ) * ), + * * @OA\Response(response="200", description="All good") * ) */ @@ -104,24 +118,29 @@ public function hid($hid) * x={ * "name": "multi" * }, + * * @OA\Parameter( * name="foo", * in="path", * required=false, * description="The foo", + * * @OA\Schema( * type="string" * ) * ), + * * @OA\Parameter( * name="bar", * in="path", * required=false, * description="The bar", + * * @OA\Schema( * type="string" * ) * ), + * * @OA\Response(response="200", description="All good") * ) */ diff --git a/tests/Fixtures/Controllers/PrefixedController.php b/tests/Fixtures/Controllers/PrefixedController.php index abdaf91..ebff64a 100644 --- a/tests/Fixtures/Controllers/PrefixedController.php +++ b/tests/Fixtures/Controllers/PrefixedController.php @@ -2,9 +2,13 @@ namespace Radebatz\OpenApi\Routing\Tests\Fixtures\Controllers; +use OpenApi\Annotations as OA; +use Radebatz\OpenApi\Extras\Annotations as OAX; + /** * @OAX\Controller( * prefix="/foo", + * * @OA\Response(response="403", description="Not allowed") * ) */ @@ -16,6 +20,7 @@ class PrefixedController * x={ * "name": "prefixed" * }, + * * @OA\Response(response="200", description="All good") * ) */ diff --git a/tests/Laravel/CachingTest.php b/tests/Laravel/CachingTest.php index 319727e..883071f 100644 --- a/tests/Laravel/CachingTest.php +++ b/tests/Laravel/CachingTest.php @@ -15,7 +15,7 @@ class CachingTest extends LaravelTestCase { use CallsApplicationTrait; - public function reloadTests() + public static function reloadTests(): iterable { return [ [null, true, false], @@ -31,6 +31,7 @@ public function reloadTests() public function testReload(?CacheInterface $cache, $reload, $openapisCached) { $options = [ + OpenApiRouter::OPTION_OA_INFO_INJECT => true, OpenApiRouter::OPTION_RELOAD => $reload, OpenApiRouter::OPTION_CACHE => $cache, ]; diff --git a/tests/Laravel/CallsApplicationTrait.php b/tests/Laravel/CallsApplicationTrait.php index 5f3c55f..94780a7 100644 --- a/tests/Laravel/CallsApplicationTrait.php +++ b/tests/Laravel/CallsApplicationTrait.php @@ -37,9 +37,12 @@ public function createApplication() ]); Facade::setFacadeApplication($app); - (new OpenApiRouter($this->getFixtureFinder(), new LaravelRoutingAdapter($app))) + $options = [ + OpenApiRouter::OPTION_OA_INFO_INJECT => true, + ]; + (new OpenApiRouter($this->getFixtureFinder(), new LaravelRoutingAdapter($app), $options)) ->registerRoutes(); - $openapi = (new OpenApiRouter($this->getFixtureFinder(), new LaravelRoutingAdapter($app))) + $openapi = (new OpenApiRouter($this->getFixtureFinder(), new LaravelRoutingAdapter($app), $options)) ->scan(); file_put_contents(__DIR__ . '/openapi.yaml', $openapi->toYaml()); diff --git a/tests/Laravel/LaravelTest.php b/tests/Laravel/LaravelTest.php index c3ba376..0913184 100644 --- a/tests/Laravel/LaravelTest.php +++ b/tests/Laravel/LaravelTest.php @@ -36,6 +36,7 @@ public function prefixed() /** * @test + * * @requires PHP 8.1 */ public function attributesPrefixed() @@ -47,6 +48,7 @@ public function attributesPrefixed() /** * @test + * * @requires PHP 8.1 */ public function attributesMiddleware() @@ -54,6 +56,6 @@ public function attributesMiddleware() $route = $this->getRouter()->getRoutes()->getByName('attributes'); $this->assertNotNull($route); - $this->assertEquals([FooMiddleware::class, BarMiddleware::class], $route->gatherMiddleware()); + $this->assertEquals([BarMiddleware::class, FooMiddleware::class], $route->gatherMiddleware()); } } diff --git a/tests/Lumen/LumenTest.php b/tests/Lumen/LumenTest.php index 9a15c7e..18e2bc8 100644 --- a/tests/Lumen/LumenTest.php +++ b/tests/Lumen/LumenTest.php @@ -38,6 +38,7 @@ public function middleware() /** * @test + * * @requires PHP 8.1 */ public function attributesPrefixed() diff --git a/tests/Slim4/CallsControllerTrait.php b/tests/Slim4/CallsControllerTrait.php index 06e1e03..a84c725 100644 --- a/tests/Slim4/CallsControllerTrait.php +++ b/tests/Slim4/CallsControllerTrait.php @@ -33,9 +33,12 @@ protected function getApp(): App { $app = AppFactory::create(); - (new OpenApiRouter($this->getFixtureFinder(), new SlimRoutingAdapter($app))) + $options = [ + OpenApiRouter::OPTION_OA_INFO_INJECT => true, + ]; + (new OpenApiRouter($this->getFixtureFinder(), new SlimRoutingAdapter($app), $options)) ->registerRoutes(); - $openapi = (new OpenApiRouter($this->getFixtureFinder(), new SlimRoutingAdapter($app))) + $openapi = (new OpenApiRouter($this->getFixtureFinder(), new SlimRoutingAdapter($app), $options)) ->scan(); file_put_contents(__DIR__ . '/openapi.yaml', $openapi->toYaml()); diff --git a/tests/Slim4/SlimTest.php b/tests/Slim4/SlimTest.php index 12c0ce1..3e6054b 100644 --- a/tests/Slim4/SlimTest.php +++ b/tests/Slim4/SlimTest.php @@ -43,6 +43,7 @@ public function middleware() /** * @test + * * @requires PHP 8.1 */ public function attributesPrefixed()