@@ -86,19 +86,19 @@ public function __invoke(array $context = []): OpenApi
86
86
$ servers = '/ ' === $ baseUrl || '' === $ baseUrl ? [new Model \Server ('/ ' )] : [new Model \Server ($ baseUrl )];
87
87
$ paths = new Model \Paths ();
88
88
$ links = [];
89
- $ schemas = [] ;
89
+ $ schemas = new \ ArrayObject () ;
90
90
91
91
foreach ($ this ->resourceNameCollectionFactory ->create () as $ resourceClass ) {
92
92
$ resourceMetadata = $ this ->resourceMetadataFactory ->create ($ resourceClass );
93
93
$ resourceShortName = $ resourceMetadata ->getShortName ();
94
94
95
95
// Items needs to be parsed first to be able to reference the lines from the collection operation
96
96
[$ itemOperationLinks , $ itemOperationSchemas ] = $ this ->collectPaths ($ resourceMetadata , $ resourceClass , OperationType::ITEM , $ context , $ paths , $ links , $ schemas );
97
- $ schemas += $ itemOperationSchemas ;
97
+ $ this -> appendSchemaDefinitions ( $ schemas, $ itemOperationSchemas) ;
98
98
[$ collectionOperationLinks , $ collectionOperationSchemas ] = $ this ->collectPaths ($ resourceMetadata , $ resourceClass , OperationType::COLLECTION , $ context , $ paths , $ links , $ schemas );
99
99
100
100
[$ subresourceOperationLinks , $ subresourceOperationSchemas ] = $ this ->collectPaths ($ resourceMetadata , $ resourceClass , OperationType::SUBRESOURCE , $ context , $ paths , $ links , $ schemas );
101
- $ schemas += $ collectionOperationSchemas ;
101
+ $ this -> appendSchemaDefinitions ( $ schemas, $ collectionOperationSchemas) ;
102
102
}
103
103
104
104
$ securitySchemes = $ this ->getSecuritySchemes ();
@@ -113,7 +113,7 @@ public function __invoke(array $context = []): OpenApi
113
113
$ servers ,
114
114
$ paths ,
115
115
new Model \Components (
116
- new \ ArrayObject ( $ schemas) ,
116
+ $ schemas ,
117
117
new \ArrayObject (),
118
118
new \ArrayObject (),
119
119
new \ArrayObject (),
@@ -128,7 +128,7 @@ public function __invoke(array $context = []): OpenApi
128
128
/**
129
129
* @return array | array
130
130
*/
131
- private function collectPaths (ResourceMetadata $ resourceMetadata , string $ resourceClass , string $ operationType , array $ context , Model \Paths $ paths , array &$ links , array $ schemas = [] ): array
131
+ private function collectPaths (ResourceMetadata $ resourceMetadata , string $ resourceClass , string $ operationType , array $ context , Model \Paths $ paths , array &$ links , \ ArrayObject $ schemas ): array
132
132
{
133
133
$ resourceShortName = $ resourceMetadata ->getShortName ();
134
134
$ operations = OperationType::COLLECTION === $ operationType ? $ resourceMetadata ->getCollectionOperations () : (OperationType::ITEM === $ operationType ? $ resourceMetadata ->getItemOperations () : $ this ->subresourceOperationFactory ->create ($ resourceClass ));
@@ -155,12 +155,14 @@ private function collectPaths(ResourceMetadata $resourceMetadata, string $resour
155
155
$ linkedOperationId = 'get ' .ucfirst ($ resourceShortName ).ucfirst (OperationType::ITEM );
156
156
$ pathItem = $ paths ->getPath ($ path ) ?: new Model \PathItem ();
157
157
$ forceSchemaCollection = OperationType::SUBRESOURCE === $ operationType ? ($ operation ['collection ' ] ?? false ) : false ;
158
+ $ schema = new Schema ('openapi ' );
159
+ $ schema ->setDefinitions ($ schemas );
158
160
159
161
$ operationOutputSchemas = [];
160
162
foreach ($ responseMimeTypes as $ operationFormat ) {
161
- $ operationOutputSchema = $ this ->jsonSchemaFactory ->buildSchema ($ resourceClass , $ operationFormat , Schema::TYPE_OUTPUT , $ operationType , $ operationName , new Schema ('openapi ' ), null , $ forceSchemaCollection );
162
- $ schemas += $ operationOutputSchema ->getDefinitions ()->getArrayCopy ();
163
+ $ operationOutputSchema = $ this ->jsonSchemaFactory ->buildSchema ($ resourceClass , $ operationFormat , Schema::TYPE_OUTPUT , $ operationType , $ operationName , $ schema , null , $ forceSchemaCollection );
163
164
$ operationOutputSchemas [$ operationFormat ] = $ operationOutputSchema ;
165
+ $ this ->appendSchemaDefinitions ($ schemas , $ operationOutputSchema ->getDefinitions ());
164
166
}
165
167
166
168
$ parameters = [];
@@ -175,19 +177,42 @@ private function collectPaths(ResourceMetadata $resourceMetadata, string $resour
175
177
// Set up parameters
176
178
if (OperationType::ITEM === $ operationType ) {
177
179
foreach ($ identifiers as $ parameterName => $ identifier ) {
178
- $ parameters [] = new Model \Parameter (\is_string ($ parameterName ) ? $ parameterName : $ identifier , 'path ' , 'Resource identifier ' , true , false , false , ['type ' => 'string ' ]);
180
+ $ parameterName = \is_string ($ parameterName ) ? $ parameterName : $ identifier ;
181
+ $ parameter = new Model \Parameter ($ parameterName , 'path ' , 'Resource identifier ' , true , false , false , ['type ' => 'string ' ]);
182
+ if ($ this ->hasParameter ($ parameter , $ parameters )) {
183
+ continue ;
184
+ }
185
+
186
+ $ parameters [] = $ parameter ;
179
187
}
180
188
$ links [$ operationId ] = $ this ->getLink ($ resourceClass , $ operationId , $ path );
181
189
} elseif (OperationType::COLLECTION === $ operationType && 'GET ' === $ method ) {
182
- $ parameters = array_merge ($ parameters , $ this ->getPaginationParameters ($ resourceMetadata , $ operationName ), $ this ->getFiltersParameters ($ resourceMetadata , $ operationName , $ resourceClass ));
190
+ foreach (array_merge ($ this ->getPaginationParameters ($ resourceMetadata , $ operationName ), $ this ->getFiltersParameters ($ resourceMetadata , $ operationName , $ resourceClass )) as $ parameter ) {
191
+ if ($ this ->hasParameter ($ parameter , $ parameters )) {
192
+ continue ;
193
+ }
194
+
195
+ $ parameters [] = $ parameter ;
196
+ }
183
197
} elseif (OperationType::SUBRESOURCE === $ operationType ) {
184
198
foreach ($ operation ['identifiers ' ] as $ parameterName => [$ class , $ property ]) {
185
- $ parameters [] = new Model \Parameter ($ parameterName , 'path ' , $ this ->resourceMetadataFactory ->create ($ class )->getShortName ().' identifier ' , true , false , false , ['type ' => 'string ' ]);
199
+ $ parameter = new Model \Parameter ($ parameterName , 'path ' , $ this ->resourceMetadataFactory ->create ($ class )->getShortName ().' identifier ' , true , false , false , ['type ' => 'string ' ]);
200
+ if ($ this ->hasParameter ($ parameter , $ parameters )) {
201
+ continue ;
202
+ }
203
+
204
+ $ parameters [] = $ parameter ;
186
205
}
187
206
188
207
if ($ operation ['collection ' ]) {
189
208
$ subresourceMetadata = $ this ->resourceMetadataFactory ->create ($ resourceClass );
190
- $ parameters = array_merge ($ parameters , $ this ->getPaginationParameters ($ resourceMetadata , $ operationName ), $ this ->getFiltersParameters ($ subresourceMetadata , $ operationName , $ resourceClass ));
209
+ foreach (array_merge ($ this ->getPaginationParameters ($ resourceMetadata , $ operationName ), $ this ->getFiltersParameters ($ subresourceMetadata , $ operationName , $ resourceClass )) as $ parameter ) {
210
+ if ($ this ->hasParameter ($ parameter , $ parameters )) {
211
+ continue ;
212
+ }
213
+
214
+ $ parameters [] = $ parameter ;
215
+ }
191
216
}
192
217
}
193
218
@@ -241,9 +266,9 @@ private function collectPaths(ResourceMetadata $resourceMetadata, string $resour
241
266
} elseif ('PUT ' === $ method || 'POST ' === $ method || 'PATCH ' === $ method ) {
242
267
$ operationInputSchemas = [];
243
268
foreach ($ requestMimeTypes as $ operationFormat ) {
244
- $ operationInputSchema = $ this ->jsonSchemaFactory ->buildSchema ($ resourceClass , $ operationFormat , Schema::TYPE_INPUT , $ operationType , $ operationName , new Schema ('openapi ' ), null , $ forceSchemaCollection );
245
- $ schemas += $ operationInputSchema ->getDefinitions ()->getArrayCopy ();
269
+ $ operationInputSchema = $ this ->jsonSchemaFactory ->buildSchema ($ resourceClass , $ operationFormat , Schema::TYPE_INPUT , $ operationType , $ operationName , $ schema , null , $ forceSchemaCollection );
246
270
$ operationInputSchemas [$ operationFormat ] = $ operationInputSchema ;
271
+ $ this ->appendSchemaDefinitions ($ schemas , $ operationInputSchema ->getDefinitions ());
247
272
}
248
273
249
274
$ requestBody = new Model \RequestBody (sprintf ('The %s %s resource ' , 'POST ' === $ method ? 'new ' : 'updated ' , $ resourceShortName ), $ this ->buildContent ($ requestMimeTypes , $ operationInputSchemas ), true );
@@ -396,7 +421,7 @@ private function getFiltersParameters(ResourceMetadata $resourceMetadata, string
396
421
$ schema ,
397
422
'array ' === $ schema ['type ' ] && \in_array ($ data ['type ' ],
398
423
[Type::BUILTIN_TYPE_ARRAY , Type::BUILTIN_TYPE_OBJECT ], true ) ? 'deepObject ' : 'form ' ,
399
- ' array ' === $ schema ['type ' ],
424
+ $ data [ ' openapi ' ][ ' explode ' ] ?? ( ' array ' === $ schema ['type ' ]) ,
400
425
$ data ['openapi ' ]['allowReserved ' ] ?? false ,
401
426
$ data ['openapi ' ]['example ' ] ?? null ,
402
427
isset ($ data ['openapi ' ]['examples ' ]
@@ -486,4 +511,25 @@ private function getSecuritySchemes(): array
486
511
487
512
return $ securitySchemes ;
488
513
}
514
+
515
+ private function appendSchemaDefinitions (\ArrayObject $ schemas , \ArrayObject $ definitions ): void
516
+ {
517
+ foreach ($ definitions as $ key => $ value ) {
518
+ $ schemas [$ key ] = $ value ;
519
+ }
520
+ }
521
+
522
+ /**
523
+ * @param Model\Parameter[] $parameters
524
+ */
525
+ private function hasParameter (Model \Parameter $ parameter , array $ parameters ): bool
526
+ {
527
+ foreach ($ parameters as $ existingParameter ) {
528
+ if ($ existingParameter ->getName () === $ parameter ->getName () && $ existingParameter ->getIn () === $ parameter ->getIn ()) {
529
+ return true ;
530
+ }
531
+ }
532
+
533
+ return false ;
534
+ }
489
535
}
0 commit comments