Skip to content

Commit 290e4e2

Browse files
LennardWesterveldfubhy
authored andcommitted
Configurable composable schemas. (#838)
1 parent 997caee commit 290e4e2

10 files changed

Lines changed: 260 additions & 30 deletions

File tree

config/schema/graphql.display.schema.yml

Lines changed: 0 additions & 3 deletions
This file was deleted.

config/schema/graphql.graphql_servers.schema.yml renamed to config/schema/graphql.schema.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,19 @@ graphql.graphql_servers.*:
2323
batching:
2424
type: boolean
2525
label: 'Batching'
26+
schema_configuration:
27+
type: 'graphql.schema.[%parent.schema]'
28+
29+
graphql.schema.*:
30+
type: mapping
31+
label: 'Schema settings'
32+
33+
graphql.schema.composable:
34+
type: mapping
35+
label: 'Composable schema'
36+
mapping:
37+
extensions:
38+
label: Enabled extensions
39+
type: sequence
40+
sequence:
41+
type: boolean
File renamed without changes.
File renamed without changes.

examples/src/Plugin/GraphQL/SchemaExtension/ExampleSchemaExtension.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
/**
1010
* @SchemaExtension(
11-
* id = "example.ext",
11+
* id = "example_extension",
12+
* name = "Example extension",
13+
* description = "A simple extension that adds node related fields.",
1214
* schema = "example"
1315
* )
1416
*/

src/Annotation/SchemaExtension.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ class SchemaExtension extends Plugin {
2020
*/
2121
public $id;
2222

23+
/**
24+
* The plugin name.
25+
*
26+
* @var string
27+
*/
28+
public $name;
29+
30+
/**
31+
* The plugin description.
32+
*
33+
* @var string
34+
*/
35+
public $description = '';
36+
2337
/**
2438
* The id of the schema plugin to extend.
2539
*

src/Entity/Server.php

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Drupal\graphql\Entity;
44

5+
use Drupal\Component\Plugin\ConfigurableInterface;
56
use Drupal\Core\Cache\CacheableDependencyInterface;
67
use Drupal\Core\Config\Entity\ConfigEntityBase;
78
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
@@ -46,6 +47,7 @@
4647
* "name",
4748
* "label",
4849
* "schema",
50+
* "schema_configuration",
4951
* "endpoint",
5052
* "debug",
5153
* "caching",
@@ -83,6 +85,13 @@ class Server extends ConfigEntityBase implements ServerInterface {
8385
*/
8486
public $schema;
8587

88+
/**
89+
* Schema configuration.
90+
*
91+
* @var array
92+
*/
93+
public $schema_configuration = [];
94+
8695
/**
8796
* Whether the server is in debug mode.
8897
*
@@ -156,27 +165,34 @@ public function executeBatch($operations) {
156165

157166
/**
158167
* {@inheritdoc}
168+
*
169+
* @throws \Drupal\Component\Plugin\Exception\PluginException
159170
*/
160171
public function configuration() {
161172
$params = \Drupal::getContainer()->getParameter('graphql.config');
173+
/** @var \Drupal\graphql\Plugin\SchemaPluginManager $manager */
162174
$manager = \Drupal::service('plugin.manager.graphql.schema');
175+
$schema = $this->get('schema');
163176

164177
/** @var \Drupal\graphql\Plugin\SchemaPluginInterface $plugin */
165-
$plugin = $manager->createInstance($this->get('schema'));
166-
$registry = $plugin->getResolverRegistry();
178+
$plugin = $manager->createInstance($schema);
179+
if ($plugin instanceof ConfigurableInterface && $config = $this->get('schema_configuration')) {
180+
$plugin->setConfiguration($config[$schema] ?? []);
181+
}
167182

168183
// Create the server config.
169-
$config = ServerConfig::create();
170-
$config->setDebug(!!$this->get('debug'));
171-
$config->setQueryBatching(!!$this->get('batching'));
172-
$config->setValidationRules($this->getValidationRules());
173-
$config->setPersistentQueryLoader($this->getPersistedQueryLoader());
174-
$config->setContext($this->getContext($plugin, $params));
175-
$config->setFieldResolver($this->getFieldResolver($registry));
176-
$config->setSchema($plugin->getSchema($registry));
177-
$config->setPromiseAdapter(new SyncPromiseAdapter());
178-
179-
return $config;
184+
$registry = $plugin->getResolverRegistry();
185+
$server = ServerConfig::create();
186+
$server->setDebug(!!$this->get('debug'));
187+
$server->setQueryBatching(!!$this->get('batching'));
188+
$server->setValidationRules($this->getValidationRules());
189+
$server->setPersistentQueryLoader($this->getPersistedQueryLoader());
190+
$server->setContext($this->getContext($plugin, $params));
191+
$server->setFieldResolver($this->getFieldResolver($registry));
192+
$server->setSchema($plugin->getSchema($registry));
193+
$server->setPromiseAdapter(new SyncPromiseAdapter());
194+
195+
return $server;
180196
}
181197

182198
/**
@@ -290,10 +306,10 @@ protected function getFieldResolver(ResolverRegistryInterface $registry) {
290306
* essential when there is a need to adjust error format, for instance
291307
* to add an additional fields or remove some of the default ones.
292308
*
293-
* @see \GraphQL\Error\FormattedError::prepareFormatter
294-
*
295309
* @return mixed|callable
296310
* The error formatter.
311+
*
312+
* @see \GraphQL\Error\FormattedError::prepareFormatter
297313
*/
298314
protected function getErrorFormatter() {
299315
return function (Error $error) {
@@ -309,10 +325,10 @@ protected function getErrorFormatter() {
309325
* Allows to replace the default error handler with a custom one. For example
310326
* when there is a need to handle specific errors differently.
311327
*
312-
* @see \GraphQL\Executor\ExecutionResult::toArray
313-
*
314328
* @return mixed|callable
315329
* The error handler.
330+
*
331+
* @see \GraphQL\Executor\ExecutionResult::toArray
316332
*/
317333
protected function getErrorHandler() {
318334
return function (array $errors, callable $formatter) {

src/Form/ServerForm.php

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22

33
namespace Drupal\graphql\Form;
44

5+
use Drupal\Component\Plugin\ConfigurableInterface;
56
use Drupal\Component\Utility\UrlHelper;
7+
use Drupal\Core\Ajax\AjaxResponse;
8+
use Drupal\Core\Ajax\ReplaceCommand;
69
use Drupal\Core\Entity\EntityForm;
710
use Drupal\Core\Entity\EntityTypeInterface;
811
use Drupal\Core\Form\FormStateInterface;
12+
use Drupal\Core\Form\SubformState;
13+
use Drupal\Core\Plugin\PluginFormInterface;
914
use Drupal\Core\Routing\RequestContext;
1015
use Drupal\graphql\Plugin\SchemaPluginManager;
1116
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -59,13 +64,33 @@ public static function create(ContainerInterface $container) {
5964
}
6065

6166
/**
62-
* {@inheritdoc}
67+
* Ajax callback triggered by the type schema select element.
68+
*
69+
* @param array $form
70+
* The form array.
71+
*
72+
* @return \Drupal\Core\Ajax\AjaxResponse
73+
* The ajax response.
6374
*/
64-
public function form(array $form, FormStateInterface $formStaet) {
65-
$form = parent::form($form, $formStaet);
75+
public function ajaxSchemaConfigurationForm(array $form) {
76+
$response = new AjaxResponse();
77+
$response->addCommand(new ReplaceCommand('#edit-schema-configuration-plugin-wrapper', $form['schema_configuration']));
6678

79+
return $response;
80+
}
81+
82+
/**
83+
* {@inheritdoc}
84+
*/
85+
public function form(array $form, FormStateInterface $formState) {
86+
$form = parent::form($form, $formState);
6787
/** @var \Drupal\graphql\Entity\ServerInterface $server */
6888
$server = $this->entity;
89+
$schemas = array_map(function ($definition) {
90+
return $definition['name'] ?? $definition['id'];
91+
}, $this->schemaManager->getDefinitions());
92+
$schema = ($formState->getUserInput()['schema'] ?: $server->get('schema')) ?: reset(array_keys($schemas));
93+
6994
if ($this->operation == 'add') {
7095
$form['#title'] = $this->t('Add server');
7196
}
@@ -96,13 +121,39 @@ public function form(array $form, FormStateInterface $formStaet) {
96121
$form['schema'] = [
97122
'#title' => t('Schema'),
98123
'#type' => 'select',
99-
'#options' => array_map(function ($definition) {
100-
return $definition['name'] ?? $definition['id'];
101-
}, $this->schemaManager->getDefinitions()),
102-
'#default_value' => $server->get('schema'),
124+
'#options' => $schemas,
125+
'#default_value' => $schema,
103126
'#description' => t('The schema to use with this server.'),
127+
'#ajax' => [
128+
'callback' => '::ajaxSchemaConfigurationForm',
129+
'progress' => [
130+
'type' => 'throbber',
131+
'message' => $this->t('Updating schema configuration form.'),
132+
],
133+
],
134+
];
135+
136+
$form['schema_configuration'] = [
137+
'#type' => 'container',
138+
'#prefix' => '<div id="edit-schema-configuration-plugin-wrapper">',
139+
'#suffix' => '</div>',
140+
'#tree' => TRUE,
104141
];
105142

143+
/* @var \Drupal\graphql\Plugin\SchemaPluginInterface $instance */
144+
$instance = $schema ? $this->schemaManager->createInstance($schema) : NULL;
145+
if ($instance instanceof PluginFormInterface && $instance instanceof ConfigurableInterface) {
146+
$instance->setConfiguration($server->get('schema_configuration')[$schema] ?? []);
147+
148+
$form['schema_configuration'][$schema] = [
149+
'#type' => 'fieldset',
150+
'#title' => $this->t('Schema configuration'),
151+
'#tree' => TRUE,
152+
];
153+
154+
$form['schema_configuration'][$schema] += $instance->buildConfigurationForm([], $formState);
155+
}
156+
106157
$form['endpoint'] = [
107158
'#title' => t('Endpoint'),
108159
'#type' => 'textfield',
@@ -148,6 +199,8 @@ public function form(array $form, FormStateInterface $formStaet) {
148199

149200
/**
150201
* {@inheritdoc}
202+
*
203+
* @throws \Drupal\Component\Plugin\Exception\PluginException
151204
*/
152205
public function validateForm(array &$form, FormStateInterface $formState) {
153206
$endpoint = &$formState->getValue('endpoint');
@@ -158,13 +211,35 @@ public function validateForm(array &$form, FormStateInterface $formState) {
158211
if ($endpoint[0] !== '/') {
159212
$formState->setErrorByName('endpoint', 'The endpoint path has to start with a forward slash.');
160213
}
161-
else if (!UrlHelper::isValid($endpoint)) {
214+
elseif (!UrlHelper::isValid($endpoint)) {
162215
$formState->setErrorByName('endpoint', 'The endpoint path contains invalid characters.');
163216
}
217+
218+
/* @var \Drupal\graphql\Plugin\SchemaPluginInterface $instance */
219+
$schema = $formState->getValue('schema');
220+
$instance = $this->schemaManager->createInstance($schema);
221+
if (!empty($form['schema_configuration'][$schema]) && $instance instanceof PluginFormInterface && $instance instanceof ConfigurableInterface) {
222+
$state = SubformState::createForSubform($form['schema_configuration'][$schema], $form, $formState);
223+
$instance->validateConfigurationForm($form['schema_configuration'][$schema], $state);
224+
}
225+
}
226+
227+
public function submitForm(array &$form, FormStateInterface $formState) {
228+
parent::submitForm($form, $formState);
229+
230+
/* @var \Drupal\graphql\Plugin\SchemaPluginInterface $instance */
231+
$schema = $formState->getValue('schema');
232+
$instance = $this->schemaManager->createInstance($schema);
233+
if ($instance instanceof PluginFormInterface && $instance instanceof ConfigurableInterface) {
234+
$state = SubformState::createForSubform($form['schema_configuration'][$schema], $form, $formState);
235+
$instance->submitConfigurationForm($form['schema_configuration'][$schema], $state);
236+
}
164237
}
165238

166239
/**
167240
* {@inheritdoc}
241+
*
242+
* @throws \Drupal\Component\Plugin\Exception\PluginException
168243
*/
169244
public function save(array $form, FormStateInterface $formState) {
170245
parent::save($form, $formState);

0 commit comments

Comments
 (0)