Skip to content

Commit 958a0ac

Browse files
committed
GraphQl-93: Implement support for variables in query
-- Variables may be Input type -- Query now accepts variables -- added type property for Output/Input Type element -- functional test was added
1 parent d7012b2 commit 958a0ac

File tree

9 files changed

+211
-19
lines changed

9 files changed

+211
-19
lines changed

app/code/Magento/GraphQl/Controller/GraphQl.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ public function dispatch(RequestInterface $request) : ResponseInterface
111111
$data = $this->jsonSerializer->unserialize($request->getContent());
112112

113113
$query = isset($data['query']) ? $data['query'] : '';
114-
114+
$variables = isset($data['variables']) ? $data['variables'] : null;
115115
// We have to extract queried field names to avoid instantiation of non necessary fields in webonyx schema
116116
// Temporal coupling is required for performance optimization
117-
$this->queryFields->setQuery($query);
117+
$this->queryFields->setQuery($query, $variables);
118118
$schema = $this->schemaGenerator->generate();
119119

120120
$result = $this->queryProcessor->process(

dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public function postQuery(string $query, array $variables = [], string $operatio
5959
$headers = array_merge($headers, ['Accept: application/json', 'Content-Type: application/json']);
6060
$requestArray = [
6161
'query' => $query,
62-
'variables' => empty($variables) ? $variables : null,
63-
'operationName' => empty($operationName) ? $operationName : null
62+
'variables' => !empty($variables) ? $variables : null,
63+
'operationName' => !empty($operationName) ? $operationName : null
6464
];
6565
$postData = $this->json->jsonEncode($requestArray);
6666

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\TestFramework\TestCase\GraphQlAbstract;
12+
use Magento\TestFramework\ObjectManager;
13+
use Magento\Catalog\Api\ProductRepositoryInterface;
14+
15+
class VariablesSupportQueryTest extends GraphQlAbstract
16+
{
17+
/**
18+
* @var ObjectManager
19+
*/
20+
private $objectManager;
21+
22+
/**
23+
* @var ProductRepositoryInterface
24+
*/
25+
private $productRepository;
26+
27+
protected function setUp()
28+
{
29+
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
30+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
31+
}
32+
33+
/**
34+
* Tests that Introspection is disabled when not in developer mode
35+
*
36+
* @magentoApiDataFixture Magento/Catalog/_files/product_simple_with_all_fields.php
37+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
38+
*/
39+
public function testQueryObjectVariablesSupport()
40+
{
41+
$productSku = 'simple';
42+
43+
$query
44+
= <<<'QUERY'
45+
query GetProductsQuery($page: Int, $filterInput: ProductFilterInput){
46+
products(
47+
pageSize: 10
48+
currentPage: $page
49+
filter: $filterInput
50+
sort: {}
51+
) {
52+
items {
53+
name
54+
}
55+
}
56+
}
57+
QUERY;
58+
$variables = [
59+
'page' => 1,
60+
'filterInput' => [
61+
'sku' => [
62+
'like' => '%simple%'
63+
]
64+
]
65+
];
66+
67+
$response = $this->graphQlQuery($query, $variables);
68+
/** @var \Magento\Catalog\Model\Product $product */
69+
$product = $this->productRepository->get($productSku, false, null, true);
70+
71+
$this->assertArrayHasKey('products', $response);
72+
$this->assertArrayHasKey('items', $response['products']);
73+
$this->assertEquals(1, count($response['products']['items']));
74+
$this->assertArrayHasKey(0, $response['products']['items']);
75+
$this->assertFields($product, $response['products']['items'][0]);
76+
}
77+
78+
/**
79+
* @param ProductInterface $product
80+
* @param array $actualResponse
81+
*/
82+
private function assertFields($product, $actualResponse)
83+
{
84+
$assertionMap = [
85+
['response_field' => 'name', 'expected_value' => $product->getName()],
86+
];
87+
88+
$this->assertResponseFields($actualResponse, $assertionMap);
89+
}
90+
}

lib/internal/Magento/Framework/GraphQl/Config.php

+10-4
Original file line numberDiff line numberDiff line change
@@ -77,18 +77,24 @@ public function getConfigElement(string $configElementName) : ConfigElementInter
7777
}
7878

7979
/**
80-
* Return all type names declared in a GraphQL schema's configuration.
80+
* Return all type names declared in a GraphQL schema's configuration and their type.
8181
*
82-
* @return string[]
82+
* @return array $types
83+
* name string
84+
* type string
8385
*/
8486
public function getDeclaredTypeNames() : array
8587
{
8688
$types = [];
8789
foreach ($this->configData->get(null) as $item) {
88-
if (isset($item['type']) && $item['type'] == 'graphql_type') {
89-
$types[] = $item['name'];
90+
if (isset($item['type'])) {
91+
$types[] = [
92+
'name' => $item['name'],
93+
'type' => $item['type'],
94+
];
9095
}
9196
}
97+
9298
return $types;
9399
}
94100
}

lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php

+18
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ class Type implements TypeInterface
2727
*/
2828
private $interfaces;
2929

30+
/**
31+
* @var string
32+
*/
33+
private $type;
34+
3035
/**
3136
* @var string
3237
*/
@@ -36,17 +41,20 @@ class Type implements TypeInterface
3641
* @param string $name
3742
* @param Field[] $fields
3843
* @param string[] $interfaces
44+
* @param string $type
3945
* @param string $description
4046
*/
4147
public function __construct(
4248
string $name,
4349
array $fields,
4450
array $interfaces,
51+
string $type,
4552
string $description
4653
) {
4754
$this->name = $name;
4855
$this->fields = $fields;
4956
$this->interfaces = $interfaces;
57+
$this->type = $type;
5058
$this->description = $description;
5159
}
5260

@@ -89,4 +97,14 @@ public function getDescription() : string
8997
{
9098
return $this->description;
9199
}
100+
101+
/**
102+
* Get a type.
103+
*
104+
* @return string
105+
*/
106+
public function getType() : string
107+
{
108+
return $this->type;
109+
}
92110
}

lib/internal/Magento/Framework/GraphQl/Config/Element/TypeFactory.php

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public function create(
9292
'name' => $typeData['name'],
9393
'fields' => $fields,
9494
'interfaces' => isset($typeData['implements']) ? $typeData['implements'] : [],
95+
'type' => isset($typeData['type']) ? $typeData['type'] : '',
9596
'description' => isset($typeData['description']) ? $typeData['description'] : ''
9697
]
9798
);

lib/internal/Magento/Framework/GraphQl/Query/Fields.php

+26-1
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ class Fields
2424
* Set Query for extracting list of fields.
2525
*
2626
* @param string $query
27+
* @param array|null $variables
28+
*
2729
* @return void
2830
*/
29-
public function setQuery($query)
31+
public function setQuery($query, $variables = null)
3032
{
3133
$queryFields = [];
3234
try {
@@ -48,6 +50,9 @@ public function setQuery($query)
4850
// It must be possible to query any fields during introspection query
4951
$queryFields = [];
5052
}
53+
if (isset($variables)) {
54+
$queryFields = array_merge($queryFields, $this->getVariables($variables));
55+
}
5156
$this->fieldsUsedInQuery = $queryFields;
5257
}
5358

@@ -62,4 +67,24 @@ public function getFieldsUsedInQuery()
6267
{
6368
return $this->fieldsUsedInQuery;
6469
}
70+
71+
/**
72+
* Extract and return list of all used fields in GraphQL query's variables
73+
*
74+
* @param array $variables
75+
*
76+
* @return string[]
77+
*/
78+
private function getVariables($variables)
79+
{
80+
$fields = [];
81+
foreach ($variables as $key => $value){
82+
if (is_array($value)){
83+
$fields = array_merge($fields, $this->getVariables($value));
84+
}
85+
$fields[$key] = $key;
86+
}
87+
88+
return $fields;
89+
}
6590
}

lib/internal/Magento/Framework/GraphQl/Schema/SchemaGenerator.php

+32-10
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
namespace Magento\Framework\GraphQl\Schema;
99

1010
use Magento\Framework\GraphQl\ConfigInterface;
11-
use Magento\Framework\GraphQl\Schema\SchemaGeneratorInterface;
12-
use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper;
1311
use Magento\Framework\GraphQl\Schema;
12+
use Magento\Framework\GraphQl\Schema\Type\Input\InputMapper;
13+
use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper;
1414
use Magento\Framework\GraphQl\SchemaFactory;
1515

1616
/**
@@ -28,6 +28,11 @@ class SchemaGenerator implements SchemaGeneratorInterface
2828
*/
2929
private $outputMapper;
3030

31+
/**
32+
* @var InputMapper
33+
*/
34+
private $inputMapper;
35+
3136
/**
3237
* @var ConfigInterface
3338
*/
@@ -36,15 +41,18 @@ class SchemaGenerator implements SchemaGeneratorInterface
3641
/**
3742
* @param SchemaFactory $schemaFactory
3843
* @param OutputMapper $outputMapper
44+
* @param InputMapper $inputMapper
3945
* @param ConfigInterface $config
4046
*/
4147
public function __construct(
4248
SchemaFactory $schemaFactory,
4349
OutputMapper $outputMapper,
50+
InputMapper $inputMapper,
4451
ConfigInterface $config
4552
) {
4653
$this->schemaFactory = $schemaFactory;
4754
$this->outputMapper = $outputMapper;
55+
$this->inputMapper = $inputMapper;
4856
$this->config = $config;
4957
}
5058

@@ -60,16 +68,30 @@ public function generate() : Schema
6068
'typeLoader' => function ($name) {
6169
return $this->outputMapper->getOutputType($name);
6270
},
63-
'types' => function () {
64-
//all types should be generated only on introspection
65-
$typesImplementors = [];
66-
foreach ($this->config->getDeclaredTypeNames() as $name) {
67-
$typesImplementors [] = $this->outputMapper->getOutputType($name);
68-
}
69-
return $typesImplementors;
70-
}
71+
'types' => $this->getTypes()
7172
]
7273
);
7374
return $schema;
7475
}
76+
77+
/**
78+
* @return array
79+
* @throws \Magento\Framework\GraphQl\Exception\GraphQlInputException
80+
*/
81+
private function getTypes()
82+
{
83+
$typesImplementors = [];
84+
foreach ($this->config->getDeclaredTypeNames() as $type) {
85+
switch ($type['type']) {
86+
case 'graphql_type' :
87+
$typesImplementors [] = $this->outputMapper->getOutputType($type['name']);
88+
break;
89+
case 'graphql_input' :
90+
$typesImplementors [] = $this->inputMapper->getInputType($type['name']);
91+
break;
92+
}
93+
}
94+
95+
return $typesImplementors;
96+
}
7597
}

lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputMapper.php

+30
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
use Magento\Framework\GraphQl\Config\Element\Argument;
1212
use Magento\Framework\GraphQl\ConfigInterface;
1313
use Magento\Framework\GraphQl\Schema\Type\ScalarTypes;
14+
use Magento\Framework\GraphQl\Schema\Type\InputTypeInterface;
1415
use Magento\Framework\GraphQl\Schema\TypeFactory;
16+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
17+
use Magento\Framework\Phrase;
1518

1619
class InputMapper
1720
{
@@ -35,6 +38,11 @@ class InputMapper
3538
*/
3639
private $scalarTypes;
3740

41+
/**
42+
* @var InputTypeInterface[]
43+
*/
44+
private $inputTypes;
45+
3846
/**
3947
* @var WrappedTypeProcessor
4048
*/
@@ -101,4 +109,26 @@ public function getRepresentation(Argument $argument) : array
101109

102110
return $calculatedArgument;
103111
}
112+
113+
/**
114+
* Get GraphQL input type object by type name.
115+
*
116+
* @param string $typeName
117+
* @return InputTypeInterface
118+
* @throws GraphQlInputException
119+
*/
120+
public function getInputType($typeName)
121+
{
122+
if (!isset($this->inputTypes[$typeName])) {
123+
$configElement = $this->config->getConfigElement($typeName);
124+
$this->inputTypes[$typeName] = $this->inputFactory->create($configElement);
125+
if (!($this->inputTypes[$typeName] instanceof InputTypeInterface)) {
126+
throw new GraphQlInputException(
127+
new Phrase("Type '{$typeName}' was requested but is not declared in the GraphQL schema.")
128+
);
129+
}
130+
}
131+
132+
return $this->inputTypes[$typeName];
133+
}
104134
}

0 commit comments

Comments
 (0)