1717use ApiPlatform \Exception \OperationNotFoundException ;
1818use ApiPlatform \GraphQl \Resolver \Factory \ResolverFactoryInterface ;
1919use ApiPlatform \GraphQl \Type \Definition \TypeInterface ;
20+ use ApiPlatform \Metadata \Extractor \DynamicResourceExtractorInterface ;
2021use ApiPlatform \Metadata \GraphQl \Mutation ;
2122use ApiPlatform \Metadata \GraphQl \Operation ;
22- use ApiPlatform \Metadata \GraphQl \Query ;
23- use ApiPlatform \Metadata \GraphQl \QueryCollection ;
2423use ApiPlatform \Metadata \GraphQl \Subscription ;
25- use ApiPlatform \Metadata \Operation as AbstractOperation ;
2624use ApiPlatform \Metadata \Property \Factory \PropertyMetadataFactoryInterface ;
2725use ApiPlatform \Metadata \Property \Factory \PropertyNameCollectionFactoryInterface ;
2826use ApiPlatform \Metadata \Resource \Factory \ResourceMetadataCollectionFactoryInterface ;
4745 */
4846final class FieldsBuilder implements FieldsBuilderInterface
4947{
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 )
5149 {
5250 }
5351
@@ -256,7 +254,25 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
256254 $ resourceClass = $ type ->getClassName ();
257255 }
258256
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 );
260276
261277 $ graphqlWrappedType = $ graphqlType instanceof WrappingType ? $ graphqlType ->getWrappedType (true ) : $ graphqlType ;
262278 $ isStandardGraphqlType = \in_array ($ graphqlWrappedType , GraphQLType::getStandardTypes (), true );
@@ -271,43 +287,22 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
271287
272288 $ args = [];
273289
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-
285290 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 );
298293 }
299294
300- $ args = $ this ->getFilterArgs ($ args , $ resourceClass , $ rootResource , $ rootOperation , $ property , $ depth , $ operation );
295+ $ args = $ this ->getFilterArgs ($ args , $ resourceClass , $ rootResource , $ resourceOperation , $ rootOperation , $ property , $ depth );
301296 }
302297
303298 if ($ isStandardGraphqlType || $ input ) {
304299 $ resolve = null ;
305300 } 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 );
307302 } elseif ($ this ->typeBuilder ->isCollection ($ type )) {
308- $ resolve = ($ this ->collectionResolverFactory )($ resourceClass , $ rootResource , $ resolverOperation );
303+ $ resolve = ($ this ->collectionResolverFactory )($ resourceClass , $ rootResource , $ resourceOperation );
309304 } else {
310- $ resolve = ($ this ->itemResolverFactory )($ resourceClass , $ rootResource , $ resolverOperation );
305+ $ resolve = ($ this ->itemResolverFactory )($ resourceClass , $ rootResource , $ resourceOperation );
311306 }
312307
313308 return [
@@ -368,21 +363,21 @@ private function getGraphQlPaginationArgs(Operation $queryOperation): array
368363 return $ args ;
369364 }
370365
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
372367 {
373- if (null === $ operation || null === $ resourceClass ) {
368+ if (null === $ resourceClass ) {
374369 return $ args ;
375370 }
376371
377- foreach ($ operation ->getFilters () ?? [] as $ filterId ) {
372+ foreach ($ resourceOperation ->getFilters () ?? [] as $ filterId ) {
378373 if (!$ this ->filterLocator ->has ($ filterId )) {
379374 continue ;
380375 }
381376
382377 foreach ($ this ->filterLocator ->get ($ filterId )->getDescription ($ resourceClass ) as $ key => $ value ) {
383378 $ nullable = isset ($ value ['required ' ]) ? !$ value ['required ' ] : true ;
384379 $ 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 );
386381
387382 if (str_ends_with ($ key , '[] ' )) {
388383 $ graphqlFilterType = GraphQLType::listOf ($ graphqlFilterType );
@@ -399,14 +394,14 @@ private function getFilterArgs(array $args, ?string $resourceClass, string $root
399394 array_walk_recursive ($ parsed , static function (&$ value ) use ($ graphqlFilterType ): void {
400395 $ value = $ graphqlFilterType ;
401396 });
402- $ args = $ this ->mergeFilterArgs ($ args , $ parsed , $ operation , $ key );
397+ $ args = $ this ->mergeFilterArgs ($ args , $ parsed , $ resourceOperation , $ key );
403398 }
404399 }
405400
406401 return $ this ->convertFilterArgsToTypes ($ args );
407402 }
408403
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
410405 {
411406 foreach ($ parsed as $ key => $ value ) {
412407 // Never override keys that cannot be merged
@@ -470,7 +465,7 @@ private function convertFilterArgsToTypes(array $args): array
470465 *
471466 * @throws InvalidTypeException
472467 */
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
474469 {
475470 $ graphqlType = $ this ->typeConverter ->convertType ($ type , $ input , $ rootOperation , $ resourceClass , $ rootResource , $ property , $ depth );
476471
@@ -487,7 +482,7 @@ private function convertType(Type $type, bool $input, Operation $rootOperation,
487482 }
488483
489484 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 );
491486 }
492487
493488 return $ forceNullable || !$ graphqlType instanceof NullableType || $ type ->isNullable () || ($ rootOperation instanceof Mutation && 'update ' === $ rootOperation ->getName ())
0 commit comments