17
17
use ApiPlatform \Exception \OperationNotFoundException ;
18
18
use ApiPlatform \GraphQl \Resolver \Factory \ResolverFactoryInterface ;
19
19
use ApiPlatform \GraphQl \Type \Definition \TypeInterface ;
20
+ use ApiPlatform \Metadata \Extractor \DynamicResourceExtractorInterface ;
20
21
use ApiPlatform \Metadata \GraphQl \Mutation ;
21
22
use ApiPlatform \Metadata \GraphQl \Operation ;
22
- use ApiPlatform \Metadata \GraphQl \Query ;
23
- use ApiPlatform \Metadata \GraphQl \QueryCollection ;
24
23
use ApiPlatform \Metadata \GraphQl \Subscription ;
25
- use ApiPlatform \Metadata \Operation as AbstractOperation ;
26
24
use ApiPlatform \Metadata \Property \Factory \PropertyMetadataFactoryInterface ;
27
25
use ApiPlatform \Metadata \Property \Factory \PropertyNameCollectionFactoryInterface ;
28
26
use ApiPlatform \Metadata \Resource \Factory \ResourceMetadataCollectionFactoryInterface ;
47
45
*/
48
46
final class FieldsBuilder implements FieldsBuilderInterface
49
47
{
50
- public function __construct (private readonly PropertyNameCollectionFactoryInterface $ propertyNameCollectionFactory , private readonly PropertyMetadataFactoryInterface $ propertyMetadataFactory , private readonly ResourceMetadataCollectionFactoryInterface $ resourceMetadataCollectionFactory , private readonly ResourceClassResolverInterface $ resourceClassResolver , private readonly TypesContainerInterface $ typesContainer , private readonly TypeBuilderInterface $ typeBuilder , private readonly TypeConverterInterface $ typeConverter , private readonly ResolverFactoryInterface $ itemResolverFactory , private readonly ResolverFactoryInterface $ collectionResolverFactory , private readonly ResolverFactoryInterface $ itemMutationResolverFactory , private readonly ResolverFactoryInterface $ itemSubscriptionResolverFactory , private readonly ContainerInterface $ filterLocator , private readonly Pagination $ pagination , private readonly ?NameConverterInterface $ nameConverter , private readonly string $ nestingSeparator )
48
+ public function __construct (private readonly PropertyNameCollectionFactoryInterface $ propertyNameCollectionFactory , private readonly PropertyMetadataFactoryInterface $ propertyMetadataFactory , private readonly ResourceMetadataCollectionFactoryInterface $ resourceMetadataCollectionFactory , private readonly DynamicResourceExtractorInterface $ dynamicResourceExtractor , private readonly ResourceClassResolverInterface $ resourceClassResolver , private readonly TypesContainerInterface $ typesContainer , private readonly TypeBuilderInterface $ typeBuilder , private readonly TypeConverterInterface $ typeConverter , private readonly ResolverFactoryInterface $ itemResolverFactory , private readonly ResolverFactoryInterface $ collectionResolverFactory , private readonly ResolverFactoryInterface $ itemMutationResolverFactory , private readonly ResolverFactoryInterface $ itemSubscriptionResolverFactory , private readonly ContainerInterface $ filterLocator , private readonly Pagination $ pagination , private readonly ?NameConverterInterface $ nameConverter , private readonly string $ nestingSeparator )
51
49
{
52
50
}
53
51
@@ -256,7 +254,25 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
256
254
$ resourceClass = $ type ->getClassName ();
257
255
}
258
256
259
- $ graphqlType = $ this ->convertType ($ type , $ input , $ rootOperation , $ resourceClass ?? '' , $ rootResource , $ property , $ depth , $ forceNullable );
257
+ $ resourceOperation = $ rootOperation ;
258
+ if ($ resourceClass && $ rootOperation ->getClass () && $ this ->resourceClassResolver ->isResourceClass ($ resourceClass ) && $ rootOperation ->getClass () !== $ resourceClass ) {
259
+ $ resourceMetadataCollection = $ this ->resourceMetadataCollectionFactory ->create ($ resourceClass );
260
+ try {
261
+ $ resourceOperation = $ resourceMetadataCollection ->getOperation ($ isCollectionType ? 'collection_query ' : 'item_query ' );
262
+ } catch (OperationNotFoundException ) {
263
+ // If there is no query operation for a nested resource, use a dynamic resource to get one.
264
+ $ dynamicResourceMetadataCollection = $ this ->resourceMetadataCollectionFactory ->create ($ this ->dynamicResourceExtractor ->addResource ($ resourceClass ));
265
+
266
+ $ resourceOperation = $ dynamicResourceMetadataCollection ->getOperation ($ isCollectionType ? 'collection_query ' : 'item_query ' )
267
+ ->withResource ($ resourceMetadataCollection [0 ]);
268
+ }
269
+ }
270
+
271
+ if (!$ resourceOperation instanceof Operation) {
272
+ throw new \LogicException ('The resource operation should be a GraphQL operation. ' );
273
+ }
274
+
275
+ $ graphqlType = $ this ->convertType ($ type , $ input , $ resourceOperation , $ rootOperation , $ resourceClass ?? '' , $ rootResource , $ property , $ depth , $ forceNullable );
260
276
261
277
$ graphqlWrappedType = $ graphqlType instanceof WrappingType ? $ graphqlType ->getWrappedType (true ) : $ graphqlType ;
262
278
$ isStandardGraphqlType = \in_array ($ graphqlWrappedType , GraphQLType::getStandardTypes (), true );
@@ -271,43 +287,22 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
271
287
272
288
$ args = [];
273
289
274
- $ resolverOperation = $ rootOperation ;
275
-
276
- if ($ resourceClass && $ this ->resourceClassResolver ->isResourceClass ($ resourceClass ) && $ rootOperation ->getClass () !== $ resourceClass ) {
277
- $ resourceMetadataCollection = $ this ->resourceMetadataCollectionFactory ->create ($ resourceClass );
278
- $ resolverOperation = $ resourceMetadataCollection ->getOperation (null , $ isCollectionType );
279
-
280
- if (!$ resolverOperation instanceof Operation) {
281
- $ resolverOperation = ($ isCollectionType ? new QueryCollection () : new Query ())->withOperation ($ resolverOperation );
282
- }
283
- }
284
-
285
290
if (!$ input && !$ rootOperation instanceof Mutation && !$ rootOperation instanceof Subscription && !$ isStandardGraphqlType && $ isCollectionType ) {
286
- if ($ this ->pagination ->isGraphQlEnabled ($ rootOperation )) {
287
- $ args = $ this ->getGraphQlPaginationArgs ($ rootOperation );
288
- }
289
-
290
- // Find the collection operation to get filters, there might be a smarter way to do this
291
- $ operation = null ;
292
- if (!empty ($ resourceClass )) {
293
- $ resourceMetadataCollection = $ this ->resourceMetadataCollectionFactory ->create ($ resourceClass );
294
- try {
295
- $ operation = $ resourceMetadataCollection ->getOperation (null , true );
296
- } catch (OperationNotFoundException ) {
297
- }
291
+ if ($ this ->pagination ->isGraphQlEnabled ($ resourceOperation )) {
292
+ $ args = $ this ->getGraphQlPaginationArgs ($ resourceOperation );
298
293
}
299
294
300
- $ args = $ this ->getFilterArgs ($ args , $ resourceClass , $ rootResource , $ rootOperation , $ property , $ depth , $ operation );
295
+ $ args = $ this ->getFilterArgs ($ args , $ resourceClass , $ rootResource , $ resourceOperation , $ rootOperation , $ property , $ depth );
301
296
}
302
297
303
298
if ($ isStandardGraphqlType || $ input ) {
304
299
$ resolve = null ;
305
300
} elseif (($ rootOperation instanceof Mutation || $ rootOperation instanceof Subscription) && $ depth <= 0 ) {
306
- $ resolve = $ rootOperation instanceof Mutation ? ($ this ->itemMutationResolverFactory )($ resourceClass , $ rootResource , $ resolverOperation ) : ($ this ->itemSubscriptionResolverFactory )($ resourceClass , $ rootResource , $ resolverOperation );
301
+ $ resolve = $ rootOperation instanceof Mutation ? ($ this ->itemMutationResolverFactory )($ resourceClass , $ rootResource , $ resourceOperation ) : ($ this ->itemSubscriptionResolverFactory )($ resourceClass , $ rootResource , $ resourceOperation );
307
302
} elseif ($ this ->typeBuilder ->isCollection ($ type )) {
308
- $ resolve = ($ this ->collectionResolverFactory )($ resourceClass , $ rootResource , $ resolverOperation );
303
+ $ resolve = ($ this ->collectionResolverFactory )($ resourceClass , $ rootResource , $ resourceOperation );
309
304
} else {
310
- $ resolve = ($ this ->itemResolverFactory )($ resourceClass , $ rootResource , $ resolverOperation );
305
+ $ resolve = ($ this ->itemResolverFactory )($ resourceClass , $ rootResource , $ resourceOperation );
311
306
}
312
307
313
308
return [
@@ -368,21 +363,21 @@ private function getGraphQlPaginationArgs(Operation $queryOperation): array
368
363
return $ args ;
369
364
}
370
365
371
- private function getFilterArgs (array $ args , ?string $ resourceClass , string $ rootResource , Operation $ rootOperation , ?string $ property , int $ depth, ? AbstractOperation $ operation = null ): array
366
+ private function getFilterArgs (array $ args , ?string $ resourceClass , string $ rootResource , Operation $ resourceOperation , Operation $ rootOperation , ?string $ property , int $ depth ): array
372
367
{
373
- if (null === $ operation || null === $ resourceClass ) {
368
+ if (null === $ resourceClass ) {
374
369
return $ args ;
375
370
}
376
371
377
- foreach ($ operation ->getFilters () ?? [] as $ filterId ) {
372
+ foreach ($ resourceOperation ->getFilters () ?? [] as $ filterId ) {
378
373
if (!$ this ->filterLocator ->has ($ filterId )) {
379
374
continue ;
380
375
}
381
376
382
377
foreach ($ this ->filterLocator ->get ($ filterId )->getDescription ($ resourceClass ) as $ key => $ value ) {
383
378
$ nullable = isset ($ value ['required ' ]) ? !$ value ['required ' ] : true ;
384
379
$ filterType = \in_array ($ value ['type ' ], Type::$ builtinTypes , true ) ? new Type ($ value ['type ' ], $ nullable ) : new Type ('object ' , $ nullable , $ value ['type ' ]);
385
- $ graphqlFilterType = $ this ->convertType ($ filterType , false , $ rootOperation , $ resourceClass , $ rootResource , $ property , $ depth );
380
+ $ graphqlFilterType = $ this ->convertType ($ filterType , false , $ resourceOperation , $ rootOperation , $ resourceClass , $ rootResource , $ property , $ depth );
386
381
387
382
if (str_ends_with ($ key , '[] ' )) {
388
383
$ graphqlFilterType = GraphQLType::listOf ($ graphqlFilterType );
@@ -399,14 +394,14 @@ private function getFilterArgs(array $args, ?string $resourceClass, string $root
399
394
array_walk_recursive ($ parsed , static function (&$ value ) use ($ graphqlFilterType ): void {
400
395
$ value = $ graphqlFilterType ;
401
396
});
402
- $ args = $ this ->mergeFilterArgs ($ args , $ parsed , $ operation , $ key );
397
+ $ args = $ this ->mergeFilterArgs ($ args , $ parsed , $ resourceOperation , $ key );
403
398
}
404
399
}
405
400
406
401
return $ this ->convertFilterArgsToTypes ($ args );
407
402
}
408
403
409
- private function mergeFilterArgs (array $ args , array $ parsed , ?AbstractOperation $ operation = null , string $ original = '' ): array
404
+ private function mergeFilterArgs (array $ args , array $ parsed , ?Operation $ operation = null , string $ original = '' ): array
410
405
{
411
406
foreach ($ parsed as $ key => $ value ) {
412
407
// Never override keys that cannot be merged
@@ -470,7 +465,7 @@ private function convertFilterArgsToTypes(array $args): array
470
465
*
471
466
* @throws InvalidTypeException
472
467
*/
473
- private function convertType (Type $ type , bool $ input , Operation $ rootOperation , string $ resourceClass , string $ rootResource , ?string $ property , int $ depth , bool $ forceNullable = false ): GraphQLType |ListOfType |NonNull
468
+ private function convertType (Type $ type , bool $ input , Operation $ resourceOperation , Operation $ rootOperation , string $ resourceClass , string $ rootResource , ?string $ property , int $ depth , bool $ forceNullable = false ): GraphQLType |ListOfType |NonNull
474
469
{
475
470
$ graphqlType = $ this ->typeConverter ->convertType ($ type , $ input , $ rootOperation , $ resourceClass , $ rootResource , $ property , $ depth );
476
471
@@ -487,7 +482,7 @@ private function convertType(Type $type, bool $input, Operation $rootOperation,
487
482
}
488
483
489
484
if ($ this ->typeBuilder ->isCollection ($ type )) {
490
- return $ this ->pagination ->isGraphQlEnabled ($ rootOperation ) && !$ input ? $ this ->typeBuilder ->getResourcePaginatedCollectionType ($ graphqlType , $ resourceClass , $ rootOperation ) : GraphQLType::listOf ($ graphqlType );
485
+ return $ this ->pagination ->isGraphQlEnabled ($ resourceOperation ) && !$ input ? $ this ->typeBuilder ->getResourcePaginatedCollectionType ($ graphqlType , $ resourceOperation ) : GraphQLType::listOf ($ graphqlType );
491
486
}
492
487
493
488
return $ forceNullable || !$ graphqlType instanceof NullableType || $ type ->isNullable () || ($ rootOperation instanceof Mutation && 'update ' === $ rootOperation ->getName ())
0 commit comments