Skip to content

GraphQL operations return null for DTO collections while REST operations work correctly #7038

Open
@lcottingham

Description

@lcottingham

API Platform version(s) affected: 4.1.x

Description
When using DTOs with collections in API Platform, there is inconsistent behavior between REST and GraphQL operations.

In REST operations, collections (both Doctrine Collections and arrays) within DTOs are automatically serialized correctly as arrays in the response.

However, in GraphQL operations, the same collections are automatically wrapped in a cursor-based pagination structure (edges/nodes) but the content is returned as null despite the collections containing data:

{
  "data": {
    "book": {
      "title": "Sample Book",
      "author": {
        "name": "John Doe"
      },
      "categories": {
        "edges": null
      }
    }
  }
}

Disabling pagination at the resource level fixes this issue by removing the cursor structure, but this should work out of the box like it does for REST operations, or at least be clearly documented how to handle this case.

How to reproduce

  1. Create a simple DTO structure:
<?php

namespace App\Dto;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\GraphQl\Query;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Attribute\Groups;

#[ApiResource(
    shortName: 'Book',
    normalizationContext: ['groups' => ['book:read']],
    graphQlOperations: [
        new Query(resolver: BookResolver::class),
    ]
)]
class Book
{
    #[Groups(['book:read'])]
    public string $title;

    #[Groups(['book:read'])]
    public Author $author;

    #[Groups(['book:read'])]
    /** @var Collection<int, Category> */
    public Collection $categories;
}
<?php

namespace App\Dto;

use ApiPlatform\Metadata\ApiResource;
use Symfony\Component\Serializer\Attribute\Groups;

#[ApiResource(operations: [], graphQlOperations: [])]
class Author 
{
    #[Groups(['book:read'])]
    public string $name;
}
<?php 

namespace App\Dto;

use ApiPlatform\Metadata\ApiResource;
use Symfony\Component\Serializer\Attribute\Groups;

#[ApiResource(operations: [], graphQlOperations: [])]
class Category
{
    #[Groups(['book:read'])]
    public string $name;
}
  1. Create a resolver for GraphQL:
<?php

namespace App\GraphQl\Resolver;

use App\Dto\Author;
use App\Dto\Book;
use App\Dto\Category;
use Doctrine\Common\Collections\ArrayCollection;
use Psr\Log\LoggerInterface;

class BookResolver
{
    public function __construct(
        private LoggerInterface $logger
    ) {
    }

    public function __invoke(): Book
    {
        $book = new Book();
        $book->title = "Sample Book";

        $author = new Author();
        $author->name = "John Doe";
        $book->author = $author;

        // Create a collection with categories
        $categories = new ArrayCollection();
        
        $category1 = new Category();
        $category1->name = "Fiction";
        $categories->add($category1);
        
        $category2 = new Category();
        $category2->name = "Adventure";
        $categories->add($category2);
        
        $book->categories = $categories;

        // Log to demonstrate the collection has content
        $this->logger->info('Categories count: ' . $categories->count());
        
        return $book;
    }
}
  1. Run a GraphQL query:
query {
  book {
    title
    author {
      name
    }
    categories {
      edges {
        node {
          name
        }
      }
    }
  }
}

The result will show the single properties correctly, but the categories collection will show edges: null despite having items in it.

Possible Solution

There are currently one workaround:

  1. Disable pagination at the resource level:
#[ApiResource(
    paginationEnabled: false,
    // ...
)]

But this workaround shouldn't be necessary. The system should either:

  1. Automatically handle collections in DTOs for GraphQL operations just like it does for REST
  2. Properly document that collections in DTOs require special configuration for GraphQL
  3. Ensure that when pagination is enabled, the collection content is still properly normalized (not returned as null)

Additional Context

In the logs, we can verify that the collection has items, but they're not appearing in the GraphQL response unless pagination is disabled.

The issue seems to be in how API Platform handles the normalization process for collections in GraphQL vs REST operations. REST operations correctly handle collections in DTOs automatically, while GraphQL operations don't unless pagination is explicitly disabled.

When pagination is enabled (the default), the collection is wrapped in a cursor-based pagination structure, but the content is returned as null, despite the collection having items.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions