Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cannot perform GraphQL sub-selections on non-doctrine resource collections #6365

Closed
igoooor opened this issue May 11, 2024 · 10 comments
Closed

Comments

@igoooor
Copy link

igoooor commented May 11, 2024

I somehow can not join the slack workspace, so I'm asking here. Also it might be a bug but I'm not sure, so please feel free to let me know where is the better place for this topic.

I am trying to perform a sub-selection query on an array/collection resource, but my resources are not doctrine entities.
If I add all doctrine annotations (just for trying), then the sub-selection works without any other changes.

Here is the code example which fails:

#[ApiResource(
    paginationEnabled: false,
    provider: UserProvider::class,
    shortName: 'user',
)]
#[QueryCollection(
    args: [
        'id' => ['type' => 'ID'],
        'what' => ['type' => 'String'],
    ],
    provider: UserCollectionProvider::class,
)]
final class UserResult
{
    /**
     * @param User[] $users
     */
    public function __construct(
        /**
         * @var User[]
         */
        public array $users,
        public Meta $meta,
        ....
    )
    {
    }
}

And the User class:

#[ApiResource(
    paginationEnabled: false,
    shortName: 'userresource',
)]
final class User
{
    public function __construct(
        public int $id,
        public string $uuid,
        public string $companyName,
        ....
    ) {
    }
}

Here is an example of query:

query searchQuery($what: String) {
  users(what: $what) {
    meta {
      totalCount
    }
    users {
      uuid
    }
  }
}

And variables:

{
  "what": "hello"
}

There is no issue with the meta sub-selection, but with the users it gives the following error:
"Field \"users\" of type \"Iterable!\" must not have a sub selection.",

As I said above, if I add all ORM annotation to convert these simple classes into entities, then the sub-selection works out of the box, with the same code and same query.
Am I missing something to tell graphql/apiplatform that $users is an array of the User::class resource?

Thank you already for your support

@soyuka
Copy link
Member

soyuka commented May 13, 2024

Can I see the UserProvider? Not sure if/how graphql works without doctrine, maybe that you need a custom resolver.

@igoooor
Copy link
Author

igoooor commented May 13, 2024

@soyuka Currently the UserProvider is quite empty, just returning a mock object.
However, the request does not even reach the provider and fails even before.

@soyuka
Copy link
Member

soyuka commented May 13, 2024

I see, inspect your schema, IIRC the type is computed at

foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
$context = [
'normalization_groups' => $operation->getNormalizationContext()['groups'] ?? null,
'denormalization_groups' => $operation->getDenormalizationContext()['groups'] ?? null,
];
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property, $context);
$propertyTypes = $propertyMetadata->getBuiltinTypes();
if (
!$propertyTypes
|| (!$input && false === $propertyMetadata->isReadable())
|| ($input && false === $propertyMetadata->isWritable())
) {
continue;
not sure why User[] can't be found, is it a resource?

@igoooor
Copy link
Author

igoooor commented May 13, 2024

Yes it has the ApiResource attribute, you can find the User class definition in my initial post.
Does it need more than the ApiResource attribute maybe?

@igoooor
Copy link
Author

igoooor commented May 13, 2024

With your comment pointing out the FieldsBuilder class, I think I was able to resolve that issue by adding the following attribute to my $users property:

#[ApiProperty(
            builtinTypes: [new Type(
                builtinType: Type::BUILTIN_TYPE_ARRAY,
                collection: true,
                collectionKeyType: new Type(builtinType: Type::BUILTIN_TYPE_INT),
                collectionValueType: new Type(builtinType: Type::BUILTIN_TYPE_OBJECT, class: User::class),
            )],
        )]

Is that a valid use of ApiProperty?

@soyuka
Copy link
Member

soyuka commented May 13, 2024

you shouldn't need this, try installing this:

composer require phpstan/phpdoc-parser

as suggested:

https://github.com/api-platform/core/blob/main/composer.json#L121

@igoooor
Copy link
Author

igoooor commented May 13, 2024

Ok so without using the ApiResource (basically the UserResult as described on the initial message) and by installing composer require phpstan/phpdoc-parser that still ends up with the same message "Field \"users\" of type \"Iterable!\" must not have a sub selection.",
I tried different syntax (no property promotion on the constructor) but same error message.
So far the only way to make it work is with adding ApiResource to my array properties.

composer.json

{
    "type": "project",
    "license": "proprietary",
    "minimum-stability": "stable",
    "prefer-stable": true,
    "require": {
        "php": ">=8.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^3.2",
        "doctrine/dbal": "^3",
        "doctrine/doctrine-bundle": "^2.12",
        "doctrine/doctrine-migrations-bundle": "^3.3",
        "doctrine/orm": "^3.1",
        "phpstan/phpdoc-parser": "^1.29",
        "symfony/asset": "7.0.*",
        "symfony/console": "*",
        "symfony/dotenv": "*",
        "symfony/flex": "^2",
        "symfony/framework-bundle": "*",
        "symfony/runtime": "*",
        "symfony/twig-bundle": "7.0.*",
        "symfony/yaml": "*",
        "webonyx/graphql-php": "^15.11"
    },
    "config": {
        "allow-plugins": {
            "php-http/discovery": true,
            "symfony/flex": true,
            "symfony/runtime": true
        },
        "sort-packages": true
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "App\\Tests\\": "tests/"
        }
    },
    "conflict": {
        "symfony/symfony": "*"
    },
    "extra": {
        "symfony": {
            "allow-contrib": false,
            "require": "7.0.*"
        }
    },
    "require-dev": {
        "phpmd/phpmd": "^2.15.0",
        "phpstan/phpstan": "^1.10.67",
        "phpstan/phpstan-doctrine": "^1.3",
        "phpstan/phpstan-phpunit": "^1.3",
        "phpstan/phpstan-symfony": "^1.3",
        "phpunit/phpunit": "^10.2",
        "squizlabs/php_codesniffer": "*",
        "symfony/maker-bundle": "^1.58"
    }
}

@soyuka
Copy link
Member

soyuka commented May 14, 2024

I assume you cleared the cache to reload the metadata after adding the phpdoc-parser?

@igoooor
Copy link
Author

igoooor commented May 14, 2024

rm -rf var/cache yes

soyuka added a commit to soyuka/core that referenced this issue May 24, 2024
@soyuka
Copy link
Member

soyuka commented May 24, 2024

could not reproduce

@soyuka soyuka closed this as completed May 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants