Skip to content

Commit babe924

Browse files
authored
Split GraphQL config from REST config (#1492)
* Split GraphQL config from REST config * Fix PHPStan * Allow to disable graphql on a per resource basis
1 parent 23c66aa commit babe924

31 files changed

+340
-260
lines changed

src/Action/GraphqlEntrypointAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public function __invoke(Request $request): Response
6767
$executionResult = new ExecutionResult(null, [$e]);
6868
}
6969

70-
return new JsonResponse($executionResult->toArray($this->debug), !$executionResult->errors ? Response::HTTP_OK : Response::HTTP_BAD_REQUEST);
70+
return new JsonResponse($executionResult->toArray($this->debug), $executionResult->errors ? Response::HTTP_BAD_REQUEST : Response::HTTP_OK);
7171
}
7272

7373
private function parseRequest(Request $request): array

src/Annotation/ApiResource.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ final class ApiResource
5353
*/
5454
public $subresourceOperations;
5555

56+
/**
57+
* @var array
58+
*/
59+
public $graphql;
60+
5661
/**
5762
* @var array
5863
*/

src/Bridge/Doctrine/Orm/SubresourceDataProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public function getSubresource(string $resourceClass, array $identifiers, array
109109
$normalizedIdentifiers = isset($identifiers[$identifier]) ? $this->normalizeIdentifiers($identifiers[$identifier], $manager, $identifierResourceClass) : [];
110110

111111
switch ($relationType) {
112-
//MANY_TO_MANY relations need an explicit join so that the identifier part can be retrieved
112+
// MANY_TO_MANY relations need an explicit join so that the identifier part can be retrieved
113113
case ClassMetadataInfo::MANY_TO_MANY:
114114
$joinAlias = $queryNameGenerator->generateJoinAlias($previousAssociationProperty);
115115

src/Bridge/Graphql/Executor.php

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

1616
use GraphQL\Executor\ExecutionResult;
1717
use GraphQL\GraphQL;
18+
use GraphQL\Type\Schema;
1819

1920
/**
2021
* Wrapper for the GraphQL facade.
@@ -28,8 +29,8 @@ final class Executor implements ExecutorInterface
2829
/**
2930
* {@inheritdoc}
3031
*/
31-
public function executeQuery(...$args): ExecutionResult
32+
public function executeQuery(Schema $schema, $source, $rootValue = null, $context = null, array $variableValues = null, string $operationName = null, callable $fieldResolver = null, array $validationRules = null): ExecutionResult
3233
{
33-
return GraphQL::executeQuery(...$args);
34+
return GraphQL::executeQuery($schema, $source, $rootValue, $context, $variableValues, $operationName, $fieldResolver, $validationRules);
3435
}
3536
}

src/Bridge/Graphql/ExecutorInterface.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\Bridge\Graphql;
1515

1616
use GraphQL\Executor\ExecutionResult;
17+
use GraphQL\Type\Schema;
1718

1819
/**
1920
* Wrapper for the GraphQL facade.
@@ -26,10 +27,6 @@ interface ExecutorInterface
2627
{
2728
/**
2829
* @see http://webonyx.github.io/graphql-php/executing-queries/#using-facade-method
29-
*
30-
* @param array ...$args
31-
*
32-
* @return ExecutionResult
3330
*/
34-
public function executeQuery(...$args): ExecutionResult;
31+
public function executeQuery(Schema $schema, $source, $rootValue = null, $context = null, array $variableValues = null, string $operationName = null, callable $fieldResolver = null, array $validationRules = null): ExecutionResult;
3532
}

src/Bridge/Graphql/Resolver/CollectionResolverFactory.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ public function __construct(CollectionDataProviderInterface $collectionDataProvi
5454
/**
5555
* @throws \Exception
5656
*/
57-
public function createCollectionResolver(string $resourceClass, string $rootClass, string $operationName): callable
57+
public function createCollectionResolver(string $resourceClass, string $rootClass): callable
5858
{
59-
return function ($root, $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) {
59+
return function ($root, $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass) {
6060
if (null !== $request = $this->requestStack->getCurrentRequest()) {
6161
$request->attributes->set(
6262
'_graphql_collections_args',
@@ -74,7 +74,7 @@ public function createCollectionResolver(string $resourceClass, string $rootClas
7474

7575
$context = $this->resourceMetadataFactory
7676
->create($resourceClass)
77-
->getCollectionOperationAttribute($operationName, 'normalization_context', [], true);
77+
->getGraphqlAttribute('query', 'normalization_context', [], true);
7878

7979
if (!$this->paginationEnabled) {
8080
$data = [];

src/Bridge/Graphql/Resolver/CollectionResolverFactoryInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@
2222
*/
2323
interface CollectionResolverFactoryInterface
2424
{
25-
public function createCollectionResolver(string $resourceClass, string $rootClass, string $operationName): callable;
25+
public function createCollectionResolver(string $resourceClass, string $rootClass): callable;
2626
}

src/Bridge/Graphql/Resolver/ItemResolverFactory.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ public function __construct(ItemDataProviderInterface $itemDataProvider, Subreso
4848
/**
4949
* @throws \Exception
5050
*/
51-
public function createItemResolver(string $resourceClass, string $rootClass, string $operationName): callable
51+
public function createItemResolver(string $resourceClass, string $rootClass): callable
5252
{
53-
return function ($root, $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) {
53+
return function ($root, $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass) {
5454
$rootProperty = $info->fieldName;
5555
$rootIdentifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($rootClass);
5656
if (isset($root[$rootProperty])) {
@@ -89,7 +89,7 @@ public function createItemResolver(string $resourceClass, string $rootClass, str
8989
null,
9090
['graphql' => true] + $this->resourceMetadataFactory
9191
->create($resourceClass)
92-
->getItemOperationAttribute($operationName, 'normalization_context', [], true)
92+
->getGraphqlAttribute('query', 'normalization_context', [], true)
9393
) : null;
9494
};
9595
}

src/Bridge/Graphql/Resolver/ItemResolverFactoryInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@
2222
*/
2323
interface ItemResolverFactoryInterface
2424
{
25-
public function createItemResolver(string $resourceClass, string $rootClass, string $operationName): callable;
25+
public function createItemResolver(string $resourceClass, string $rootClass): callable;
2626
}

src/Bridge/Graphql/Type/SchemaBuilder.php

Lines changed: 38 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
use GraphQL\Type\Definition\WrappingType;
3030
use GraphQL\Type\Schema;
3131
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
32-
use Symfony\Component\HttpFoundation\Request;
3332
use Symfony\Component\PropertyInfo\Type;
3433

3534
/**
@@ -67,9 +66,13 @@ public function __construct(PropertyNameCollectionFactoryInterface $propertyName
6766
public function getSchema(): Schema
6867
{
6968
$queryFields = [];
69+
foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
70+
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
71+
if (!isset($resourceMetadata->getGraphql()['query'])) {
72+
continue;
73+
}
7074

71-
foreach ($this->resourceNameCollectionFactory->create() as $resource) {
72-
$queryFields += $this->getQueryFields($resource);
75+
$queryFields += $this->getQueryFields($resourceClass, $resourceMetadata);
7376
}
7477

7578
return new Schema([
@@ -83,44 +86,41 @@ public function getSchema(): Schema
8386
/**
8487
* Gets the query fields of the schema.
8588
*/
86-
private function getQueryFields(string $resource): array
89+
private function getQueryFields(string $resourceClass, ResourceMetadata $resourceMetadata): array
8790
{
8891
$queryFields = [];
89-
$resourceMetadata = $this->resourceMetadataFactory->create($resource);
9092
$shortName = $resourceMetadata->getShortName();
9193

92-
foreach ($this->getOperations($resourceMetadata, true, true) as $operationName => $queryItemOperation) {
93-
$fieldNamePrefix = 'get' === $operationName ? '' : $operationName;
94-
if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resource), $resource, $operationName)) {
95-
$fieldConfiguration['args'] += $this->getResourceIdentifiersArgumentsConfiguration($resource, $operationName);
96-
$queryFields[lcfirst($fieldNamePrefix.$shortName)] = $fieldConfiguration;
97-
}
94+
if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass)) {
95+
$fieldConfiguration['args'] += $this->getResourceIdentifiersArgumentsConfiguration($resourceClass);
96+
$queryFields[lcfirst($shortName)] = $fieldConfiguration;
9897
}
9998

100-
foreach ($this->getOperations($resourceMetadata, true, false) as $operationName => $queryCollectionOperation) {
101-
$fieldNamePrefix = 'get' === $operationName ? '' : $operationName;
102-
if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resource)), $resource, $operationName)) {
103-
$queryFields[lcfirst($fieldNamePrefix.Inflector::pluralize($shortName))] = $fieldConfiguration;
104-
}
99+
if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass)) {
100+
$queryFields[lcfirst(Inflector::pluralize($shortName))] = $fieldConfiguration;
105101
}
106102

107103
return $queryFields;
108104
}
109105

110106
/**
111-
* Get the field configuration of a resource.
107+
* Gets the field configuration of a resource.
112108
*
113109
* @see http://webonyx.github.io/graphql-php/type-system/object-types/
114110
*
115111
* @return array|null
116112
*/
117-
private function getResourceFieldConfiguration(string $fieldDescription = null, Type $type, string $rootResource, string $operationName, bool $isInput = false)
113+
private function getResourceFieldConfiguration(string $fieldDescription = null, Type $type, string $rootResource, bool $isInput = false)
118114
{
119115
try {
120-
$graphqlType = $this->convertType($type, $operationName, $isInput);
116+
$graphqlType = $this->convertType($type, $isInput);
121117
$graphqlWrappedType = $graphqlType instanceof WrappingType ? $graphqlType->getWrappedType() : $graphqlType;
122118
$isInternalGraphqlType = in_array($graphqlWrappedType, GraphQLType::getInternalTypes(), true);
123-
$className = $isInternalGraphqlType ? '' : ($type->isCollection() ? $type->getCollectionValueType()->getClassName() : $type->getClassName());
119+
if ($isInternalGraphqlType) {
120+
$className = '';
121+
} else {
122+
$className = $type->isCollection() ? $type->getCollectionValueType()->getClassName() : $type->getClassName();
123+
}
124124

125125
$args = [];
126126
if ($this->paginationEnabled && !$isInternalGraphqlType && $type->isCollection() && !$isInput) {
@@ -139,7 +139,7 @@ private function getResourceFieldConfiguration(string $fieldDescription = null,
139139
if ($isInternalGraphqlType || $isInput) {
140140
$resolve = null;
141141
} else {
142-
$resolve = $type->isCollection() ? $this->collectionResolverFactory->createCollectionResolver($className, $rootResource, $operationName) : $this->itemResolverFactory->createItemResolver($className, $rootResource, $operationName);
142+
$resolve = $type->isCollection() ? $this->collectionResolverFactory->createCollectionResolver($className, $rootResource) : $this->itemResolverFactory->createItemResolver($className, $rootResource);
143143
}
144144

145145
return [
@@ -158,21 +158,21 @@ private function getResourceFieldConfiguration(string $fieldDescription = null,
158158
*
159159
* @throws \LogicException
160160
*/
161-
private function getResourceIdentifiersArgumentsConfiguration(string $resource, string $operationName): array
161+
private function getResourceIdentifiersArgumentsConfiguration(string $resourceClass): array
162162
{
163163
$arguments = [];
164-
$identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($resource);
164+
$identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass);
165165
foreach ($identifiers as $identifier) {
166-
$propertyMetadata = $this->propertyMetadataFactory->create($resource, $identifier);
166+
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $identifier);
167167
$propertyType = $propertyMetadata->getType();
168168
if (null === $propertyType) {
169169
continue;
170170
}
171171

172-
$arguments[$identifier] = $this->getResourceFieldConfiguration($propertyMetadata->getDescription(), $propertyType, $resource, $operationName, true);
172+
$arguments[$identifier] = $this->getResourceFieldConfiguration($propertyMetadata->getDescription(), $propertyType, $resourceClass, true);
173173
}
174174
if (!$arguments) {
175-
throw new \LogicException("Missing identifier field for resource \"$resource\".");
175+
throw new \LogicException("Missing identifier field for resource \"$resourceClass\".");
176176
}
177177

178178
return $arguments;
@@ -183,7 +183,7 @@ private function getResourceIdentifiersArgumentsConfiguration(string $resource,
183183
*
184184
* @throws InvalidTypeException
185185
*/
186-
private function convertType(Type $type, string $operationName, bool $isInput = false): GraphQLType
186+
private function convertType(Type $type, bool $isInput = false): GraphQLType
187187
{
188188
switch ($type->getBuiltinType()) {
189189
case Type::BUILTIN_TYPE_BOOL:
@@ -211,7 +211,7 @@ private function convertType(Type $type, string $operationName, bool $isInput =
211211
throw new InvalidTypeException();
212212
}
213213

214-
$graphqlType = $this->getResourceObjectType($className, $resourceMetadata, $operationName, $isInput);
214+
$graphqlType = $this->getResourceObjectType($className, $resourceMetadata, $isInput);
215215
break;
216216
default:
217217
throw new InvalidTypeException();
@@ -229,19 +229,18 @@ private function convertType(Type $type, string $operationName, bool $isInput =
229229
*
230230
* @return ObjectType|InputObjectType
231231
*/
232-
private function getResourceObjectType(string $resource, ResourceMetadata $resourceMetadata, string $operationName, bool $isInput = false)
232+
private function getResourceObjectType(string $resource, ResourceMetadata $resourceMetadata, bool $isInput = false)
233233
{
234234
$shortName = $resourceMetadata->getShortName().($isInput ? 'Input' : '');
235-
236235
if (isset($this->resourceTypesCache[$shortName])) {
237236
return $this->resourceTypesCache[$shortName];
238237
}
239238

240239
$configuration = [
241240
'name' => $shortName,
242241
'description' => $resourceMetadata->getDescription(),
243-
'fields' => function () use ($resource, $operationName, $isInput) {
244-
return $this->getResourceObjectTypeFields($resource, $operationName, $isInput);
242+
'fields' => function () use ($resource, $isInput) {
243+
return $this->getResourceObjectTypeFields($resource, $isInput);
245244
},
246245
];
247246

@@ -251,18 +250,16 @@ private function getResourceObjectType(string $resource, ResourceMetadata $resou
251250
/**
252251
* Gets the fields of the type of the given resource.
253252
*/
254-
private function getResourceObjectTypeFields(string $resource, string $operationName, bool $isInput = false): array
253+
private function getResourceObjectTypeFields(string $resource, bool $isInput = false): array
255254
{
256255
$fields = [];
257-
258256
foreach ($this->propertyNameCollectionFactory->create($resource) as $property) {
259257
$propertyMetadata = $this->propertyMetadataFactory->create($resource, $property);
260-
if (null === ($propertyType = $propertyMetadata->getType())
261-
|| !$propertyMetadata->isReadable()) {
258+
if (null === ($propertyType = $propertyMetadata->getType()) || !$propertyMetadata->isReadable()) {
262259
continue;
263260
}
264261

265-
if ($fieldConfiguration = $this->getResourceFieldConfiguration($propertyMetadata->getDescription(), $propertyType, $resource, $operationName, $isInput)) {
262+
if ($fieldConfiguration = $this->getResourceFieldConfiguration($propertyMetadata->getDescription(), $propertyType, $resource, $isInput)) {
266263
$fields[$property] = $fieldConfiguration;
267264
}
268265
}
@@ -286,7 +283,7 @@ private function getResourcePaginatedCollectionType($resourceType, bool $isInput
286283
}
287284

288285
$edgeObjectTypeConfiguration = [
289-
'name' => $shortName.'Edge',
286+
'name' => "{$shortName}Edge",
290287
'description' => "Edge of $shortName.",
291288
'fields' => [
292289
'node' => $resourceType,
@@ -295,7 +292,7 @@ private function getResourcePaginatedCollectionType($resourceType, bool $isInput
295292
];
296293
$edgeObjectType = $isInput ? new InputObjectType($edgeObjectTypeConfiguration) : new ObjectType($edgeObjectTypeConfiguration);
297294
$pageInfoObjectTypeConfiguration = [
298-
'name' => $shortName.'PageInfo',
295+
'name' => "{$shortName}PageInfo",
299296
'description' => 'Information about the current page.',
300297
'fields' => [
301298
'endCursor' => GraphQLType::string(),
@@ -305,37 +302,14 @@ private function getResourcePaginatedCollectionType($resourceType, bool $isInput
305302
$pageInfoObjectType = $isInput ? new InputObjectType($pageInfoObjectTypeConfiguration) : new ObjectType($pageInfoObjectTypeConfiguration);
306303

307304
$configuration = [
308-
'name' => $shortName.'Connection',
305+
'name' => "{$shortName}Connection",
309306
'description' => "Connection for $shortName.",
310307
'fields' => [
311308
'edges' => GraphQLType::listOf($edgeObjectType),
312309
'pageInfo' => GraphQLType::nonNull($pageInfoObjectType),
313310
],
314311
];
315312

316-
return $this->resourceTypesCache[$shortName.'Connection'] = $isInput ? new InputObjectType($configuration) : new ObjectType($configuration);
317-
}
318-
319-
/**
320-
* Get the available operations for a resource.
321-
*/
322-
private function getOperations(ResourceMetadata $resourceMetadata, bool $isQuery, bool $isItem): \Traversable
323-
{
324-
$operations = $isItem ? $resourceMetadata->getItemOperations() : $resourceMetadata->getCollectionOperations();
325-
if (null === $operations) {
326-
return yield from [];
327-
}
328-
329-
foreach ($operations as $operationName => $operation) {
330-
if (isset($operation['controller']) || !isset($operation['method'])) {
331-
continue;
332-
}
333-
334-
if ($isQuery && Request::METHOD_GET !== $operation['method'] || !$isQuery && Request::METHOD_GET === $operation['method']) {
335-
continue;
336-
}
337-
338-
yield $operationName => $operation;
339-
}
313+
return $this->resourceTypesCache["{$shortName}Connection"] = $isInput ? new InputObjectType($configuration) : new ObjectType($configuration);
340314
}
341315
}

src/DataProvider/SubresourceDataProviderInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ interface SubresourceDataProviderInterface
2727
*
2828
* @param string $resourceClass The root resource class
2929
* @param array $identifiers Identifiers and their values
30-
* @param array $context The context indicate the conjuction between collection properties (identifiers) and their class
30+
* @param array $context The context indicates the conjunction between collection properties (identifiers) and their class
3131
* @param string $operationName
3232
*
3333
* @throws ResourceClassNotSupportedException

src/Metadata/Extractor/AbstractExtractor.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ public function getResources(): array
5050

5151
/**
5252
* Extracts metadata from a given path.
53-
*
54-
* @param string $path
5553
*/
5654
abstract protected function extractPath(string $path);
5755
}

0 commit comments

Comments
 (0)