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?
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;
}
}