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

[GraphQl] Support for unions and interfaces for search functionality #2927

Open
samzijlmans opened this issue Jul 11, 2019 · 2 comments
Open

Comments

@samzijlmans
Copy link

samzijlmans commented Jul 11, 2019

I am trying to create a general search functionality in my app that can return multiple types of objects. For each type of object I want to retrieve different fields.

In the graphQl specification this is supported using interfaces and unions. See this example below:

type Query {
    # Search across all content
    search(query: String!): [SearchResult]
}

union SearchResult = Conference | Festival | Concert | Venue
query {
  search(query: "Madison") {
    ... on Venue {
      id
      name
      address
    }

    ... on Festival {
      id
      name
      performers
    }

    ... on Concert {
      id
      name
      performingBand
    }

    ... on Conference {
      speakers
      workshops
    }
  }
}

See this guide for more details.

I tried making all my entities extend a 'base' entity and creating the search query on that entity, but that did not work.

Thanks in advance!

@samzijlmans samzijlmans changed the title GraphQl: Support for unions and interfaces for search functionality [GraphQl] Support for unions and interfaces for search functionality Jul 11, 2019
@codedge
Copy link

codedge commented Nov 1, 2023

Is this something that is planned or any advice how to deal with it meanwhile? This rules out a big part of GraphQL capabilities.

@shanginn
Copy link

would love to see this implemented.
in a meantime I got fragments working for collection for my setup like this.

I have different types, not OneComment and AnotherComment. this is just for demonstration

// api/src/Entity/Comment.php
//...
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap([
    'one'            => OneComment::class,
    'another'        => AnotherComment::class,
])]
class Comment
{
//...
// api/src/Entity/OneComment.php
//...
class OneComment extends Comment
{
//...
// api/src/Entity/AnotherComment.php
//...
class AnotherComment extends Comment
{
//...
# api/config/services.yaml:
# ...
    App\Type\TypeConverter:
        decorates: api_platform.graphql.type_converter

    App\Type\UnionCommentType:
        tags:
            - { name: api_platform.graphql.type }

    ApiPlatform\GraphQl\Type\TypeBuild
// api/src/Type/TypeConverter.php
<?php

namespace App\Type;

use ApiPlatform\GraphQl\Type\TypeConverterInterface;
use ApiPlatform\Metadata\GraphQl\Operation;
use App\Entity\Comment;
use GraphQL\Type\Definition\Type as GraphQLType;
use Symfony\Component\PropertyInfo\Type;

final class TypeConverter implements TypeConverterInterface
{
    public function __construct(
        private TypeConverterInterface $defaultTypeConverter,
        private UnionCommentType $unionCommentType,
    ) {
    }

    /**
     * {@inheritdoc}
     */
    public function convertType(
        Type $type,
        bool $input,
        Operation $rootOperation,
        string $resourceClass,
        string $rootResource,
        ?string $property,
        int $depth
    ): GraphQLType|string|null {
        if ($type->isCollection() && $type->getCollectionValueTypes()[0]->getClassName() === Comment::class) {
            return $this->unionCommentType;
        }

        return $this->defaultTypeConverter->convertType(
            type: $type,
            input: $input,
            rootOperation: $rootOperation,
            resourceClass: $resourceClass,
            rootResource: $rootResource,
            property: $property,
            depth: $depth,
        );
    }

    /**
     * {@inheritdoc}
     */
    public function resolveType(string $type): ?GraphQLType
    {
        return $this->defaultTypeConverter->resolveType($type);
    }
}
// api/src/Type/UnionCommentType.php
<?php

namespace App\Type;

use ApiPlatform\GraphQl\Type\Definition\TypeInterface;
use ApiPlatform\GraphQl\Type\TypeBuilderEnumInterface;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use App\Entity\OneComment;
use App\Entity\AnotherComment;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\UnionType;

final class UnionCommentType extends UnionType implements TypeInterface
{
    /**
     * @var array<string, ObjectType>
     */
    private array $commentTypes;

    public function __construct(
        private readonly TypeBuilderEnumInterface $typeBuilder,
        private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
    ) {
        $this->name              = 'UnionComment';
        $this->description       = 'Union type for all Comment types';
        $this->astNode           = null;
        $this->extensionASTNodes = [];

        $this->commentTypes = [
            OneComment::class            => $this->getCommentType(OneComment::class),
            AnotherComment::class        => $this->getCommentType(AnotherComment::class),
        ];

        $this->config = [
            'name'              => 'UnionComment',
            'description'       => 'Union type for all Comment types',
            'types'             => $this->commentTypes,
            'resolveType'       => fn ($objectValue) => $this->commentTypes[$objectValue['#itemResourceClass']],
            'astNode'           => null,
            'extensionASTNodes' => [],
        ];
    }

    public function getName(): string
    {
        return 'UnionComment';
    }

    private function getCommentType(string $resourceClass): ObjectType
    {
        $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);

        $operationName = 'collection_query';
        $operation     = $resourceMetadataCollection->getOperation($operationName);

        assert($operation instanceof QueryCollection);

        $type = $this->typeBuilder->getResourceObjectType(
            resourceClass: $resourceClass,
            resourceMetadataCollection: $resourceMetadataCollection,
            operation: $operation,
            input: false,
            wrapped: false,
            depth: 0
        );

        assert($type instanceof ObjectType);

        return $type;
    }
}
query {
  comments() {
    collection {
      id
      ... on OneComment {
        id
        body
      }
      ... on AnotherComment {
        id
        body
      }
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants