Description
API Platform version(s) affected: 3.1, 3.2
Description
GraphQL Query operation ignores custom ItemProvider if resource does not have Http Get operation
src/Entity/Account.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GraphQl\Query;
use App\Repository\AccountRepository;
use App\State\AccountItemProvider;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: AccountRepository::class)]
#[ApiResource(
operations: [],
normalizationContext: ['groups' => ['account:read_normalization']],
)]
#[Get(provider: AccountItemProvider::class)] //<--- !!!
#[Query(provider: AccountItemProvider::class)]
class Account
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ApiProperty(identifier: true)]
#[Groups(['account:read_normalization'])]
private Uuid $id;
#[ORM\Column(type: "json")]
#[Groups(['account:read_normalization'])]
private array $credentials = [];
}
src/State/AccountItemProvider.php
namespace App\State;
use ApiPlatform\Doctrine\Orm\State\ItemProvider;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class AccountItemProvider implements ProviderInterface
{
private ItemProvider $decoratedProvider;
private AuthorizationCheckerInterface $authorizationChecker;
public function __construct(ItemProvider $decoratedProvider, AuthorizationCheckerInterface $authorizationChecker)
{
$this->decoratedProvider = $decoratedProvider;
$this->authorizationChecker = $authorizationChecker;
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?object
{
$provider = $this->decoratedProvider->provide($operation, $uriVariables, $context);
if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
$provider->setCredentials([]);
}
return $provider;
}
}
For example, I have an entry with ID 018a7bb5-4215-7363-82cf-b54c804c138e
and a property credentials
that contains the following data:
[
{
"key": "1234567890"
}
]
GraphQL query:
query getAccount{
account(id: "/accounts/018a7bb5-4215-7363-82cf-b54c804c138e"){
id
credentials
}
}
The result will be as follows:
{
"data": {
"account": {
"id": "/accounts/018a7bb5-4215-7363-82cf-b54c804c138e",
"credentials": []
}
}
}
However, if you remove the HTTP Get operation attribute, the ApiPlatform\Doctrine\Orm\State\ItemProvider
provider will be used instead of App\State\AccountProvider
, resulting in the following result:
{
"data": {
"account": {
"id": "/accounts/018a7bb5-4215-7363-82cf-b54c804c138e",
"credentials": [
{
"key": "1234567890"
}
]
}
}
}
How to reproduce
Implement your own ItemProvider and configure a GraphQL Query operation without an Http Get operation
Possible Solution
...
Additional Context
Perhaps I have to use QueryItemRessolver?
use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
class AccountItemResolver implements QueryItemResolverInterface
{
private AuthorizationCheckerInterface $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
public function __invoke($item, array $context): object
{
if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
$item->setCredentials([]);
}
return $item;
}
}
#[Query(resolver: AccountItemResolver::class)]
This works, but, note that there is no similar problem if I use the GraphQL QueryCollection operation with custom CollectionProvider without the HTTP GetCollection operation