This issue is related to #2740 and #2687, but it's different enough that I felt it deserves a separate issue.
I'm having trouble performing sub-selection queries on non-doctrine, non-resource collections. Here's an example. Say I have a very simple resource class:
/**
* @ApiResource
*/
class Book
{
/**
* @ApiProperty(identifier=true)
*
* @var string
*/
public $id;
/**
* @var Chapter[]
*/
public $chapters;
}
with Chapter is a simple POPO:
class Chapter
{
/**
* @var int
*/
public $startingPage;
/**
* @var string
*/
public $name;
}
For testing purposes I have custom data provider that serves dummy instances of books, each with several chapters. I also have a Normalizer that handles normalizing Chapters. This works well initially:

But say I want to fetch only the chapter names, I run into the following error:
Field \"chapters\" of type \"Iterable!\" must not have a sub selection.

This is a contrived example, of course, but you can imagine some of the properties on the nested object being bandwidth-heavy or expensive to compute.
I saw that there has been quite a bit of refactoring of the GraphQL code in master, but unfortunately I'm experiencing this issue with both the 2.4 branch as well as master. I also tried to go through the GraphQL code but I'll admit that it's a bit over my head.
Any guidance would be appreciated. Thanks!
Today the schema is built only with the resources. That's why there is this issue.
I plan to do it relatively soon since it's a very asked feature.
Creating a custom type (from ObjectType) then using TypeConverter to replace the default type with the custom type seems to work for me.
Yes it can be a solution.
Creating a custom type (from ObjectType) then using TypeConverter to replace the default type with the custom type seems to work for me.
@hoangnd25 any chance you could share a little pseudo-code showing your custom type and TypeConverter? Would really appreciate any tips you could offer.
You could create custom type like this
<?php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
class ChapterType extends ObjectType
{
public function __construct()
{
parent::__construct([
'name' => 'Chapter',
'fields' => [
'startingPage' => [
'type' => Type::int(),
],
'name' => [
'type' => Type::string()
],
],
'resolveField' => [$this, 'resolve']
]);
}
public function resolve($root, $args, $context, ResolveInfo $resolveInfo)
{
if ($resolveInfo->fieldName === 'startingPage') {
return $root['startingPage'];
}
}
}
Then use TypeConverter to replace it
<?php
final class TypeConverter implements TypeConverterInterface
{
public function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, string $resourceClass, string $rootResource, ?string $property, int $depth)
{
if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType()
&& is_a($type->getClassName(), Chapter::class, true)
) {
return 'Chapter';
// or use the type container
// return $this->typeContainer->get(ChapterType::class)
}
return $this->defaultTypeConverter->convertType($type, $input, $queryName, $mutationName, $resourceClass, $rootResource, $property, $depth);
}
}
You would need to extend ObjectType or equivalent, ApiPlatform\Core\GraphQl\Type\Definition\TypeInterface only works for scalar types
You might need to create a normalizer for Chapter as well.
This is just a hack imo.
Thank you, @hoangnd25! I will give this a shot. I agree that it's a bit of a hack, but hopefully it'll be a stop-gap until @alanpoulain and crew hook us up with a more elegant solution.
You would need to extend
ObjectTypeor equivalent,ApiPlatform\Core\GraphQl\Type\Definition\TypeInterfaceonly works for scalar types
That's where I got hung up before. Thanks for the tip.
Should be solved with https://github.com/api-platform/core/pull/3327.
@alanpoulain perhaps you or someone else can share a correct way on configuring the annotations on the model classes? Or is there some example for this case?
Setting the child property to @var ChildClass[] still results in Iterable for me, turning it into Collection<ChildClass> does result in a correct Introspection result however query results are empty in this case while the returned object from the dataprovider definitely isn't.
Tested with api-platform/core version 2.5.4 and 2.5.5
Thanks!
I had this issue with a regular object type.
<?php
declare(strict_types=1);
namespace App\Type\Definition;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use ApiPlatform\Core\GraphQl\Type\Definition\TypeInterface;
final class SeoPropsType extends ObjectType implements TypeInterface
{
public function __construct(array $config = [])
{
$config['fields'] = [
'title' => Type::string(),
'description' => Type::string(),
];
parent::__construct($config);
}
public function getName(): string
{
return $this->name;
}
public function serialize($value)
{
return $value;
}
public function parseValue($value)
{
return $value;
}
public function parseLiteral($valueNode, ?array $variables = null)
{
return $valueNode;
}
}
because TypeInterface extends LeafType and it causes the error.
The solution was to not implement TypeInterface and register the type manually
_(because only TypeInterface implementations are registered automatically)_
final class SeoPropsType extends ObjectType { ... }
services:
App\Type\Definition\SeoPropsType:
tags: [ 'api_platform.graphql.type' ]
Isn't TypeInterface extends LeafType a bug?
Because this is a conflict
MyType extends ObjectType implements TypeInterface馃LeafType
Object is not a Leaf
@oleg-brizy fixed in https://github.com/api-platform/core/pull/3621.
@alanpoulain Thanks. But I can't use it because it's not merged and I can't merge it in my fork because I get conflicts.