Search code examples
phpsymfonygraphqlapi-platform.com

Return custom collection in graphQL with Api Platform using custom data provider


I have a custom DTO class, and I want to return a collection of that using graphQL. With REST it works fine. I'm using Api Platform 2.6 and PHP 8.2 There is my DTO class :

<?php

declare(strict_types=1);

namespace App\Dto;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;

#[ApiResource(
    collectionOperations: [
        'get' => [
            'method' => 'GET',
            'path' => '/settings',
        ],
    ],
    itemOperations: [
        'get' => [
            'method' => 'GET',
            'path' => '/settings/{key}',
        ],
    ],
    routePrefix: '/admin',
)]
final class SettingDto
{
    #[ApiProperty(identifier: true)]
    public string $key;

    public string $value;
}

There is my custom data provider :

<?php

declare(strict_types=1);

namespace App\Api\DataProvider;

use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Dto\SettingDto;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

final readonly class SettingDataProvider implements ItemDataProviderInterface, CollectionDataProviderInterface, RestrictedDataProviderInterface
{
    public function __construct(
        private ParameterBagInterface $parameterBag,
        private PropertyAccessorInterface $propertyAccessor,
    )
    {}

    public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
    {
        return is_a($resourceClass, SettingDto::class, true);
    }

    /**
     * @inheritDoc
     */
    public function getCollection(string $resourceClass, string $operationName = null): array
    {
        return array_map(static function ($key, $value) {
            $settingDto = new SettingDto();
            $settingDto->key = $key;
            $settingDto->value = $value;

            return $settingDto;
        }, array_keys($this->parameterBag->get('exposed.parameters')), $this->parameterBag->get('exposed.parameters'));
    }

    public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?SettingDto
    {
        $setting = $this->propertyAccessor->getValue($this->parameterBag->get('exposed.parameters'), "[$id]");
        if (null === $setting) {
            return null;
        }

        $settingDto = new SettingDto();
        $settingDto->key = $id;
        $settingDto->value = $setting;

        return $settingDto;
    }
}

It works fine using REST but when I try to use graphQL I have this error :

Collection returned by the collection data provider must implement ApiPlatform\\Core\\DataProvider\\PaginatorInterface or ApiPlatform\\Core\\DataProvider\\PartialPaginatorInterface

I have also tried to use custom resolver like mentioned here instead but it's not working either. Any ideas?


Solution

  • I've finally found a solution, I used ArrayPaginator, and I've done some changes in my DTO too, so now I have my pagination back (and in REST too) :

    <?php
    
    declare(strict_types=1);
    
    namespace App\Dto;
    
    use ApiPlatform\Core\Annotation\ApiProperty;
    use ApiPlatform\Core\Annotation\ApiResource;
    
    #[ApiResource(
        collectionOperations: [
            'get' => [
                'method' => 'GET',
                'path' => '/settings',
            ],
        ],
        graphql: [
            'item_query',
            'collection_query' => [
                'pagination_type' => 'page',
            ],
        ],
        itemOperations: [
            'get' => [
                'method' => 'GET',
                'path' => '/settings/{key}',
            ],
        ],
        routePrefix: '/admin',
    )]
    final class SettingDto
    {
        #[ApiProperty(identifier: true)]
        public string $key;
    
        public string $value;
    }
    

    And my dataProvider :

    <?php
    
    declare(strict_types=1);
    
    namespace App\Api\DataProvider;
    
    use ApiPlatform\Core\DataProvider\ArrayPaginator;
    use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
    use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
    use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
    use App\Dto\SettingDto;
    use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
    use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
    
    final readonly class SettingDataProvider implements ItemDataProviderInterface, ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
    {
        public function __construct(
            private ParameterBagInterface $parameterBag,
            private PropertyAccessorInterface $propertyAccessor,
        )
        {}
    
        public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
        {
            return is_a($resourceClass, SettingDto::class, true);
        }
    
        /**
         * @inheritDoc
         */
        public function getCollection(string $resourceClass, string $operationName = null, array $context = []): ArrayPaginator
        {
            $page = $this->propertyAccessor->getValue($context, "[filters][page]") ?? 1;
            $itemsPerPage = $this->propertyAccessor->getValue($context, "[filters][itemsPerPage]") ?? 10;
            $firstResult = ($page -1) * $itemsPerPage;
    
            $settings = array_map(static function ($key, $value) {
                $settingDto = new SettingDto();
                $settingDto->key = $key;
                $settingDto->value = $value;
    
                return $settingDto;
            }, array_keys($this->parameterBag->get('exposed.parameters')), $this->parameterBag->get('exposed.parameters'));
    
            return new ArrayPaginator($settings, $firstResult, $itemsPerPage);
        }
    
        public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?SettingDto
        {
            $setting = $this->propertyAccessor->getValue($this->parameterBag->get('exposed.parameters'), "[$id]");
            if (null === $setting) {
                return null;
            }
    
            $settingDto = new SettingDto();
            $settingDto->key = $id;
            $settingDto->value = $setting;
    
            return $settingDto;
        }
    }