@@ -124,6 +124,11 @@ final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareI
124
124
],
125
125
];
126
126
127
+ /**
128
+ * @var array<string, bool>
129
+ */
130
+ private $ builtSchema = [];
131
+
127
132
public function __construct (private readonly SchemaFactoryInterface $ schemaFactory , private readonly PropertyMetadataFactoryInterface $ propertyMetadataFactory , ResourceClassResolverInterface $ resourceClassResolver , ?ResourceMetadataCollectionFactoryInterface $ resourceMetadataFactory = null , private ?DefinitionNameFactoryInterface $ definitionNameFactory = null )
128
133
{
129
134
if (!$ definitionNameFactory ) {
@@ -163,12 +168,12 @@ public function buildSchema(string $className, string $format = 'jsonapi', strin
163
168
// We don't use the serializer context here as JSON:API doesn't leverage serializer groups for related resources.
164
169
// That is done by query parameter. @see https://jsonapi.org/format/#fetching-includes
165
170
$ jsonApiSerializerContext = $ serializerContext ;
166
- if (false === ($ serializerContext [self ::DISABLE_JSON_SCHEMA_SERIALIZER_GROUPS ] ?? true )) {
171
+ if (true === ($ serializerContext [self ::DISABLE_JSON_SCHEMA_SERIALIZER_GROUPS ] ?? true ) && $ inputOrOutputClass === $ className ) {
167
172
unset($ jsonApiSerializerContext ['groups ' ]);
168
173
}
169
174
170
175
$ schema = $ this ->schemaFactory ->buildSchema ($ className , 'json ' , $ type , $ operation , $ schema , $ jsonApiSerializerContext , $ forceCollection );
171
- $ definitionName = $ this ->definitionNameFactory ->create ($ className , $ format , $ className , $ operation , $ serializerContext );
176
+ $ definitionName = $ this ->definitionNameFactory ->create ($ inputOrOutputClass , $ format , $ className , $ operation , $ jsonApiSerializerContext );
172
177
$ prefix = $ this ->getSchemaUriPrefix ($ schema ->getVersion ());
173
178
$ definitions = $ schema ->getDefinitions ();
174
179
$ collectionKey = $ schema ->getItemsDefinitionKey ();
@@ -183,11 +188,6 @@ public function buildSchema(string $className, string $format = 'jsonapi', strin
183
188
$ key = $ schema ->getRootDefinitionKey () ?? $ collectionKey ;
184
189
$ properties = $ definitions [$ definitionName ]['properties ' ] ?? [];
185
190
186
- // Prevent reapplying
187
- if (isset ($ definitions [$ key ]['description ' ])) {
188
- $ definitions [$ definitionName ]['description ' ] = $ definitions [$ key ]['description ' ];
189
- }
190
-
191
191
if (Error::class === $ className && !isset ($ properties ['errors ' ])) {
192
192
$ definitions [$ definitionName ]['properties ' ] = [
193
193
'errors ' => [
@@ -213,41 +213,41 @@ public function buildSchema(string $className, string $format = 'jsonapi', strin
213
213
return $ schema ;
214
214
}
215
215
216
- if (($ schema ['type ' ] ?? '' ) === 'array ' ) {
217
- if (!isset ($ definitions [self ::COLLECTION_BASE_SCHEMA_NAME ])) {
218
- $ definitions [self ::COLLECTION_BASE_SCHEMA_NAME ] = [
219
- 'type ' => 'object ' ,
220
- 'properties ' => [
221
- 'links ' => self ::LINKS_PROPS ,
222
- 'meta ' => self ::META_PROPS ,
223
- 'data ' => [
224
- 'type ' => 'array ' ,
225
- ],
226
- ],
227
- 'required ' => ['data ' ],
228
- ];
229
- }
230
-
231
- unset($ schema ['items ' ]);
232
- unset($ schema ['type ' ]);
233
-
234
- $ properties = $ this ->buildDefinitionPropertiesSchema ($ key , $ className , $ format , $ type , $ operation , $ schema , []);
235
- $ properties ['data ' ]['properties ' ]['attributes ' ]['$ref ' ] = $ prefix .$ key ;
216
+ if (($ schema ['type ' ] ?? '' ) !== 'array ' ) {
217
+ return $ schema ;
218
+ }
236
219
237
- $ schema ['description ' ] = "$ definitionName collection. " ;
238
- $ schema ['allOf ' ] = [
239
- ['$ref ' => $ prefix .self ::COLLECTION_BASE_SCHEMA_NAME ],
240
- ['type ' => 'object ' , 'properties ' => [
220
+ if (!isset ($ definitions [self ::COLLECTION_BASE_SCHEMA_NAME ])) {
221
+ $ definitions [self ::COLLECTION_BASE_SCHEMA_NAME ] = [
222
+ 'type ' => 'object ' ,
223
+ 'properties ' => [
224
+ 'links ' => self ::LINKS_PROPS ,
225
+ 'meta ' => self ::META_PROPS ,
241
226
'data ' => [
242
227
'type ' => 'array ' ,
243
- 'items ' => $ properties ['data ' ],
244
228
],
245
- ]],
229
+ ],
230
+ 'required ' => ['data ' ],
246
231
];
247
-
248
- return $ schema ;
249
232
}
250
233
234
+ unset($ schema ['items ' ]);
235
+ unset($ schema ['type ' ]);
236
+
237
+ $ properties = $ this ->buildDefinitionPropertiesSchema ($ key , $ className , $ format , $ type , $ operation , $ schema , []);
238
+ $ properties ['data ' ]['properties ' ]['attributes ' ]['$ref ' ] = $ prefix .$ key ;
239
+
240
+ $ schema ['description ' ] = "$ definitionName collection. " ;
241
+ $ schema ['allOf ' ] = [
242
+ ['$ref ' => $ prefix .self ::COLLECTION_BASE_SCHEMA_NAME ],
243
+ ['type ' => 'object ' , 'properties ' => [
244
+ 'data ' => [
245
+ 'type ' => 'array ' ,
246
+ 'items ' => $ properties ['data ' ],
247
+ ],
248
+ ]],
249
+ ];
250
+
251
251
return $ schema ;
252
252
}
253
253
@@ -279,8 +279,23 @@ private function buildDefinitionPropertiesSchema(string $key, string $className,
279
279
$ inputOrOutputClass = $ this ->findOutputClass ($ relatedClassName , $ type , $ operation , $ serializerContext );
280
280
$ serializerContext ??= $ this ->getSerializerContext ($ operation , $ type );
281
281
$ definitionName = $ this ->definitionNameFactory ->create ($ relatedClassName , $ format , $ inputOrOutputClass , $ operation , $ serializerContext );
282
- $ ref = $ this ->getSchemaUriPrefix ($ schema ->getVersion ()).$ definitionName ;
283
- $ refs [$ ref ] = '$ref ' ;
282
+
283
+ // to avoid recursion
284
+ if ($ this ->builtSchema [$ definitionName ] ?? false ) {
285
+ $ refs [$ this ->getSchemaUriPrefix ($ schema ->getVersion ()).$ definitionName ] = '$ref ' ;
286
+ continue ;
287
+ }
288
+
289
+ if (!isset ($ definitions [$ definitionName ])) {
290
+ $ this ->builtSchema [$ definitionName ] = true ;
291
+ $ subSchema = new Schema ($ schema ->getVersion ());
292
+ $ subSchema ->setDefinitions ($ schema ->getDefinitions ());
293
+ $ subSchema = $ this ->buildSchema ($ relatedClassName , $ format , $ type , $ operation , $ subSchema , $ serializerContext + [self ::FORCE_SUBSCHEMA => true ], false );
294
+ $ schema ->setDefinitions ($ subSchema ->getDefinitions ());
295
+ $ definitions = $ schema ->getDefinitions ();
296
+ }
297
+
298
+ $ refs [$ this ->getSchemaUriPrefix ($ schema ->getVersion ()).$ definitionName ] = '$ref ' ;
284
299
}
285
300
$ relatedDefinitions [$ propertyName ] = array_flip ($ refs );
286
301
if ($ isOne ) {
@@ -327,24 +342,6 @@ private function buildDefinitionPropertiesSchema(string $key, string $className,
327
342
];
328
343
}
329
344
330
- if ($ required = $ definitions [$ key ]['required ' ] ?? null ) {
331
- foreach ($ required as $ i => $ require ) {
332
- if (isset ($ relationships [$ require ])) {
333
- $ replacement ['relationships ' ]['required ' ][] = $ require ;
334
- unset($ required [$ i ]);
335
- }
336
- }
337
-
338
- $ replacement ['attributes ' ] = [
339
- 'allOf ' => [
340
- $ replacement ['attributes ' ],
341
- ['type ' => 'object ' , 'required ' => $ required ],
342
- ],
343
- ];
344
-
345
- unset($ definitions [$ key ]['required ' ]);
346
- }
347
-
348
345
return [
349
346
'data ' => [
350
347
'type ' => 'object ' ,
0 commit comments