Skip to content

Commit d22ec5f

Browse files
committed
feat(graphql): graphql resource metadata collection
1 parent b27d221 commit d22ec5f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1200
-772
lines changed

phpstan.neon.dist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ parameters:
4646
ignoreErrors:
4747
# False positives
4848
- '#Parameter \#1 \$callback of function call_user_func expects callable\(\): mixed, non-empty-string given\.#'
49+
-
50+
message: '#Unreachable statement - code above always terminates.#'
51+
paths:
52+
- tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php
4953
-
5054
message: "#Parameter \\#2 \\$dqlPart of method Doctrine\\\\ORM\\\\QueryBuilder::add\\(\\) expects array<'join'\\|int, array<int\\|string, object>\\|string>\\|object\\|string, array\\('o' => Doctrine\\\\ORM\\\\Query\\\\Expr\\\\Join\\) given\\.#"
5155
paths:

src/Core/GraphQl/Resolver/Factory/CollectionResolverFactory.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface;
1919
use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityStageInterface;
2020
use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface;
21-
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2221
use ApiPlatform\Core\Util\CloneTrait;
22+
use ApiPlatform\Exception\OperationNotFoundException;
23+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2324
use GraphQL\Type\Definition\ResolveInfo;
2425
use Psr\Container\ContainerInterface;
2526
use Symfony\Component\HttpFoundation\RequestStack;
@@ -43,17 +44,17 @@ final class CollectionResolverFactory implements ResolverFactoryInterface
4344
private $serializeStage;
4445
private $queryResolverLocator;
4546
private $requestStack;
46-
private $resourceMetadataFactory;
47+
private $resourceMetadataCollectionFactory;
4748

48-
public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, ResourceMetadataFactoryInterface $resourceMetadataFactory, RequestStack $requestStack = null)
49+
public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, RequestStack $requestStack = null)
4950
{
5051
$this->readStage = $readStage;
5152
$this->securityStage = $securityStage;
5253
$this->securityPostDenormalizeStage = $securityPostDenormalizeStage;
5354
$this->serializeStage = $serializeStage;
5455
$this->queryResolverLocator = $queryResolverLocator;
5556
$this->requestStack = $requestStack;
56-
$this->resourceMetadataFactory = $resourceMetadataFactory;
57+
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
5758
}
5859

5960
public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable
@@ -79,9 +80,14 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul
7980
throw new \LogicException('Collection from read stage should be iterable.');
8081
}
8182

82-
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
83+
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
84+
try {
85+
$operation = $resourceMetadataCollection->getGraphQlOperation($operationName);
86+
$queryResolverId = $operation->getResolver();
87+
} catch (OperationNotFoundException $e) {
88+
$queryResolverId = null;
89+
}
8390

84-
$queryResolverId = $resourceMetadata->getGraphqlAttribute($operationName, 'collection_query');
8591
if (null !== $queryResolverId) {
8692
/** @var QueryCollectionResolverInterface $queryResolver */
8793
$queryResolver = $this->queryResolverLocator->get($queryResolverId);

src/Core/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface;
2222
use ApiPlatform\Core\GraphQl\Resolver\Stage\ValidateStageInterface;
2323
use ApiPlatform\Core\GraphQl\Resolver\Stage\WriteStageInterface;
24-
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2524
use ApiPlatform\Core\Util\ClassInfoTrait;
2625
use ApiPlatform\Core\Util\CloneTrait;
26+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2727
use GraphQL\Type\Definition\ResolveInfo;
2828
use Psr\Container\ContainerInterface;
2929

@@ -48,9 +48,9 @@ final class ItemMutationResolverFactory implements ResolverFactoryInterface
4848
private $writeStage;
4949
private $validateStage;
5050
private $mutationResolverLocator;
51-
private $resourceMetadataFactory;
51+
private $resourceMetadataCollectionFactory;
5252

53-
public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, DeserializeStageInterface $deserializeStage, WriteStageInterface $writeStage, ValidateStageInterface $validateStage, ContainerInterface $mutationResolverLocator, ResourceMetadataFactoryInterface $resourceMetadataFactory)
53+
public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, DeserializeStageInterface $deserializeStage, WriteStageInterface $writeStage, ValidateStageInterface $validateStage, ContainerInterface $mutationResolverLocator, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory)
5454
{
5555
$this->readStage = $readStage;
5656
$this->securityStage = $securityStage;
@@ -60,7 +60,7 @@ public function __construct(ReadStageInterface $readStage, SecurityStageInterfac
6060
$this->writeStage = $writeStage;
6161
$this->validateStage = $validateStage;
6262
$this->mutationResolverLocator = $mutationResolverLocator;
63-
$this->resourceMetadataFactory = $resourceMetadataFactory;
63+
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
6464
}
6565

6666
public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable
@@ -97,15 +97,16 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul
9797

9898
$item = ($this->deserializeStage)($item, $resourceClass, $operationName, $resolverContext);
9999

100-
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
100+
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
101+
$operation = $resourceMetadataCollection->getGraphQlOperation($operationName);
101102

102-
$mutationResolverId = $resourceMetadata->getGraphqlAttribute($operationName, 'mutation');
103+
$mutationResolverId = $operation->getResolver();
103104
if (null !== $mutationResolverId) {
104105
/** @var MutationResolverInterface $mutationResolver */
105106
$mutationResolver = $this->mutationResolverLocator->get($mutationResolverId);
106107
$item = $mutationResolver($item, $resolverContext);
107108
if (null !== $item && $resourceClass !== $itemClass = $this->getObjectClass($item)) {
108-
throw new \LogicException(sprintf('Custom mutation resolver "%s" has to return an item of class %s but returned an item of class %s.', $mutationResolverId, $resourceMetadata->getShortName(), (new \ReflectionClass($itemClass))->getShortName()));
109+
throw new \LogicException(sprintf('Custom mutation resolver "%s" has to return an item of class %s but returned an item of class %s.', $mutationResolverId, $operation->getShortName(), (new \ReflectionClass($itemClass))->getShortName()));
109110
}
110111
}
111112

src/Core/GraphQl/Resolver/Factory/ItemResolverFactory.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface;
1919
use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityStageInterface;
2020
use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface;
21-
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2221
use ApiPlatform\Core\Util\ClassInfoTrait;
2322
use ApiPlatform\Core\Util\CloneTrait;
23+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2424
use GraphQL\Type\Definition\ResolveInfo;
2525
use Psr\Container\ContainerInterface;
2626

@@ -43,16 +43,16 @@ final class ItemResolverFactory implements ResolverFactoryInterface
4343
private $securityPostDenormalizeStage;
4444
private $serializeStage;
4545
private $queryResolverLocator;
46-
private $resourceMetadataFactory;
46+
private $resourceMetadataCollectionFactory;
4747

48-
public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, ResourceMetadataFactoryInterface $resourceMetadataFactory)
48+
public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory)
4949
{
5050
$this->readStage = $readStage;
5151
$this->securityStage = $securityStage;
5252
$this->securityPostDenormalizeStage = $securityPostDenormalizeStage;
5353
$this->serializeStage = $serializeStage;
5454
$this->queryResolverLocator = $queryResolverLocator;
55-
$this->resourceMetadataFactory = $resourceMetadataFactory;
55+
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
5656
}
5757

5858
public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable
@@ -72,9 +72,10 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul
7272
}
7373

7474
$resourceClass = $this->getResourceClass($item, $resourceClass);
75-
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
75+
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
76+
$operation = $resourceMetadataCollection->getGraphQlOperation($operationName);
7677

77-
$queryResolverId = $resourceMetadata->getGraphqlAttribute($operationName, 'item_query');
78+
$queryResolverId = $operation->getResolver();
7879
if (null !== $queryResolverId) {
7980
/** @var QueryItemResolverInterface $queryResolver */
8081
$queryResolver = $this->queryResolverLocator->get($queryResolverId);

src/Core/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface;
1919
use ApiPlatform\Core\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface;
2020
use ApiPlatform\Core\GraphQl\Subscription\SubscriptionManagerInterface;
21-
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2221
use ApiPlatform\Core\Util\ClassInfoTrait;
2322
use ApiPlatform\Core\Util\CloneTrait;
23+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2424
use GraphQL\Type\Definition\ResolveInfo;
2525

2626
/**
@@ -38,16 +38,16 @@ final class ItemSubscriptionResolverFactory implements ResolverFactoryInterface
3838
private $readStage;
3939
private $securityStage;
4040
private $serializeStage;
41-
private $resourceMetadataFactory;
41+
private $resourceMetadataCollectionFactory;
4242
private $subscriptionManager;
4343
private $mercureSubscriptionIriGenerator;
4444

45-
public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SerializeStageInterface $serializeStage, ResourceMetadataFactoryInterface $resourceMetadataFactory, SubscriptionManagerInterface $subscriptionManager, ?MercureSubscriptionIriGeneratorInterface $mercureSubscriptionIriGenerator)
45+
public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SerializeStageInterface $serializeStage, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, SubscriptionManagerInterface $subscriptionManager, ?MercureSubscriptionIriGeneratorInterface $mercureSubscriptionIriGenerator)
4646
{
4747
$this->readStage = $readStage;
4848
$this->securityStage = $securityStage;
4949
$this->serializeStage = $serializeStage;
50-
$this->resourceMetadataFactory = $resourceMetadataFactory;
50+
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
5151
$this->subscriptionManager = $subscriptionManager;
5252
$this->mercureSubscriptionIriGenerator = $mercureSubscriptionIriGenerator;
5353
}
@@ -75,9 +75,10 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul
7575

7676
$subscriptionId = $this->subscriptionManager->retrieveSubscriptionId($resolverContext, $result);
7777

78-
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
78+
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
79+
$operation = $resourceMetadataCollection->getGraphQlOperation($operationName);
7980

80-
if ($subscriptionId && ($mercure = $resourceMetadata->getAttribute('mercure', false))) {
81+
if ($subscriptionId && ($mercure = $operation->getMercure())) {
8182
if (!$this->mercureSubscriptionIriGenerator) {
8283
throw new \LogicException('Cannot use Mercure for subscriptions when MercureBundle is not installed. Try running "composer require mercure".');
8384
}

src/Core/GraphQl/Resolver/ResourceFieldResolver.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414
namespace ApiPlatform\Core\GraphQl\Resolver;
1515

16-
use ApiPlatform\Core\Api\IriConverterInterface;
16+
use ApiPlatform\Api\IriConverterInterface;
17+
use ApiPlatform\Api\UrlGeneratorInterface;
1718
use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer;
1819
use ApiPlatform\Core\Util\ClassInfoTrait;
1920
use GraphQL\Type\Definition\ResolveInfo;
@@ -40,7 +41,7 @@ public function __invoke(?array $source, array $args, $context, ResolveInfo $inf
4041
{
4142
$property = null;
4243
if ('id' === $info->fieldName && !isset($source['_id']) && isset($source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY])) {
43-
return $this->iriConverter->getItemIriFromResourceClass($source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY]);
44+
return $this->iriConverter->getIriFromResourceClass($source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY], null, UrlGeneratorInterface::ABS_PATH, ['identifiers_values' => $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY], 'force_collection' => false]);
4445
}
4546

4647
if ('_id' === $info->fieldName && !isset($source['_id']) && isset($source['id'])) {

src/Core/GraphQl/Resolver/Stage/DeserializeStage.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer;
1717
use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface;
18-
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
18+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
1919
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
2020
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
2121

@@ -28,13 +28,13 @@
2828
*/
2929
final class DeserializeStage implements DeserializeStageInterface
3030
{
31-
private $resourceMetadataFactory;
31+
private $resourceMetadataCollectionFactory;
3232
private $denormalizer;
3333
private $serializerContextBuilder;
3434

35-
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, DenormalizerInterface $denormalizer, SerializerContextBuilderInterface $serializerContextBuilder)
35+
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, DenormalizerInterface $denormalizer, SerializerContextBuilderInterface $serializerContextBuilder)
3636
{
37-
$this->resourceMetadataFactory = $resourceMetadataFactory;
37+
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
3838
$this->denormalizer = $denormalizer;
3939
$this->serializerContextBuilder = $serializerContextBuilder;
4040
}
@@ -44,8 +44,9 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa
4444
*/
4545
public function __invoke($objectToPopulate, string $resourceClass, string $operationName, array $context)
4646
{
47-
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
48-
if (!$resourceMetadata->getGraphqlAttribute($operationName, 'deserialize', true, true)) {
47+
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
48+
$operation = $resourceMetadataCollection->getGraphQlOperation($operationName);
49+
if (!$operation->canDeserialize()) {
4950
return $objectToPopulate;
5051
}
5152

0 commit comments

Comments
 (0)