4949use ApiPlatform \OpenApi \Model \Response ;
5050use ApiPlatform \OpenApi \Model \SecurityScheme ;
5151use ApiPlatform \OpenApi \Model \Server ;
52+ use ApiPlatform \OpenApi \Model \Tag ;
5253use ApiPlatform \OpenApi \OpenApi ;
5354use ApiPlatform \OpenApi \Options ;
5455use ApiPlatform \OpenApi \Serializer \NormalizeOperationNameTrait ;
@@ -69,6 +70,7 @@ final class OpenApiFactory implements OpenApiFactoryInterface
6970 use TypeFactoryTrait;
7071
7172 public const BASE_URL = 'base_url ' ;
73+ public const API_PLATFORM_TAG = 'x-apiplatform-tag ' ;
7274 public const OVERRIDE_OPENAPI_RESPONSES = 'open_api_override_responses ' ;
7375 private readonly Options $ openApiOptions ;
7476 private readonly PaginationOptions $ paginationOptions ;
@@ -101,6 +103,10 @@ public function __construct(
101103
102104 /**
103105 * {@inheritdoc}
106+ *
107+ * You can filter openapi operations with the `x-apiplatform-tag` on an OpenApi Operation using the `filter_tags`.
108+ *
109+ * @param array{base_url?: string, filter_tags?: string[]}&array<string, mixed> $context
104110 */
105111 public function __invoke (array $ context = []): OpenApi
106112 {
@@ -112,12 +118,13 @@ public function __invoke(array $context = []): OpenApi
112118 $ paths = new Paths ();
113119 $ schemas = new \ArrayObject ();
114120 $ webhooks = new \ArrayObject ();
121+ $ tags = [];
115122
116123 foreach ($ this ->resourceNameCollectionFactory ->create () as $ resourceClass ) {
117124 $ resourceMetadataCollection = $ this ->resourceMetadataFactory ->create ($ resourceClass );
118125
119126 foreach ($ resourceMetadataCollection as $ resourceMetadata ) {
120- $ this ->collectPaths ($ resourceMetadata , $ resourceMetadataCollection , $ paths , $ schemas , $ webhooks , $ context );
127+ $ this ->collectPaths ($ resourceMetadata , $ resourceMetadataCollection , $ paths , $ schemas , $ webhooks , $ tags , $ context );
121128 }
122129 }
123130
@@ -128,6 +135,8 @@ public function __invoke(array $context = []): OpenApi
128135 $ securityRequirements [] = [$ key => []];
129136 }
130137
138+ $ globalTags = $ this ->openApiOptions ->getTags () ?: array_values ($ tags ) ?: [];
139+
131140 return new OpenApi (
132141 $ info ,
133142 $ servers ,
@@ -142,19 +151,25 @@ public function __invoke(array $context = []): OpenApi
142151 new \ArrayObject ($ securitySchemes )
143152 ),
144153 $ securityRequirements ,
145- [] ,
154+ $ globalTags ,
146155 null ,
147156 null ,
148157 $ webhooks
149158 );
150159 }
151160
152- private function collectPaths (ApiResource $ resource , ResourceMetadataCollection $ resourceMetadataCollection , Paths $ paths , \ArrayObject $ schemas , \ArrayObject $ webhooks , array $ context = []): void
161+ private function collectPaths (ApiResource $ resource , ResourceMetadataCollection $ resourceMetadataCollection , Paths $ paths , \ArrayObject $ schemas , \ArrayObject $ webhooks , array & $ tags , array $ context = []): void
153162 {
154163 if (0 === $ resource ->getOperations ()->count ()) {
155164 return ;
156165 }
157166
167+ // This filters on our extension x-apiplatform-tag as the openapi operation tag is used for ordering operations
168+ $ filteredTags = $ context ['filter_tags ' ] ?? [];
169+ if (!\is_array ($ filteredTags )) {
170+ $ filteredTags = [$ filteredTags ];
171+ }
172+
158173 foreach ($ resource ->getOperations () as $ operationName => $ operation ) {
159174 $ resourceShortName = $ operation ->getShortName ();
160175 // No path to return
@@ -169,6 +184,15 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection
169184 continue ;
170185 }
171186
187+ $ operationTag = ($ openapiAttribute ?->getExtensionProperties()[self ::API_PLATFORM_TAG ] ?? []);
188+ if (!\is_array ($ operationTag )) {
189+ $ operationTag = [$ operationTag ];
190+ }
191+
192+ if ($ filteredTags && $ filteredTags !== array_intersect ($ filteredTags , $ operationTag )) {
193+ continue ;
194+ }
195+
172196 $ resourceClass = $ operation ->getClass () ?? $ resource ->getClass ();
173197 $ routeName = $ operation ->getRouteName () ?? $ operation ->getName ();
174198
@@ -217,6 +241,10 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection
217241 extensionProperties: $ openapiOperation ->getExtensionProperties (),
218242 );
219243
244+ foreach ($ openapiOperation ->getTags () as $ v ) {
245+ $ tags [$ v ] = new Tag (name: $ v , description: $ resource ->getDescription ());
246+ }
247+
220248 [$ requestMimeTypes , $ responseMimeTypes ] = $ this ->getMimeTypes ($ operation );
221249
222250 if ($ path ) {
0 commit comments