Skip to content

Commit 4f92c53

Browse files
authored
feat(schema): Allow altering of schemas by introducing alter events in a new base class (#1301) (#1302)
1 parent 3be06ac commit 4f92c53

File tree

8 files changed

+555
-0
lines changed

8 files changed

+555
-0
lines changed

src/Event/AlterSchemaDataEvent.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Drupal\graphql\Event;
6+
7+
use Drupal\Component\EventDispatcher\Event;
8+
9+
/**
10+
* Represents an event that is triggered to alter schema data.
11+
*/
12+
class AlterSchemaDataEvent extends Event {
13+
14+
/**
15+
* Event fired to alter schema data.
16+
*
17+
* @var string
18+
*/
19+
const EVENT_NAME = 'graphql.sdl.alter_schema';
20+
21+
/**
22+
* The schema array data.
23+
*
24+
* @var array
25+
*/
26+
protected $schemaData;
27+
28+
/**
29+
* AlterSchemaDataEvent constructor.
30+
*
31+
* @param array $schemaData
32+
* The schema data reference.
33+
*/
34+
public function __construct(array &$schemaData) {
35+
$this->schemaData = $schemaData;
36+
}
37+
38+
/**
39+
* Returns the schema data.
40+
*
41+
* @return array
42+
* The schema data.
43+
*/
44+
public function getSchemaData(): array {
45+
return $this->schemaData;
46+
}
47+
48+
/**
49+
* Sets the schema data.
50+
*
51+
* @param array $schemaData
52+
* The schema data.
53+
*/
54+
public function setSchemaData(array $schemaData): void {
55+
$this->schemaData = $schemaData;
56+
}
57+
58+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Drupal\graphql\Event;
6+
7+
use Drupal\Component\EventDispatcher\Event;
8+
9+
/**
10+
* Represents an event that is triggered to alter schema extension data.
11+
*/
12+
class AlterSchemaExtensionDataEvent extends Event {
13+
14+
/**
15+
* Event fired to alter schema extension data.
16+
*
17+
* @var string
18+
*/
19+
const EVENT_NAME = 'graphql.sdl.alter_schema_extension';
20+
21+
/**
22+
* The schema array data.
23+
*
24+
* @var array
25+
*/
26+
protected $schemaExtensionData;
27+
28+
/**
29+
* AlterSchemaExtensionDataEvent constructor.
30+
*
31+
* @param array $schemaExtensionData
32+
* The schema extension data.
33+
*/
34+
public function __construct(array $schemaExtensionData) {
35+
$this->schemaExtensionData = $schemaExtensionData;
36+
}
37+
38+
/**
39+
* Returns the schema extension data.
40+
*
41+
* @return array
42+
* The schema extension data.
43+
*/
44+
public function getSchemaExtensionData(): array {
45+
return $this->schemaExtensionData;
46+
}
47+
48+
/**
49+
* Returns the schema extension data.
50+
*
51+
* @param array $schemaExtensionData
52+
* The schema extension data.
53+
*/
54+
public function setSchemaExtensionData(array $schemaExtensionData): void {
55+
$this->schemaExtensionData = $schemaExtensionData;
56+
}
57+
58+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?php
2+
3+
namespace Drupal\graphql\Plugin\GraphQL\Schema;
4+
5+
use Drupal\Core\Cache\CacheBackendInterface;
6+
use Drupal\graphql\Event\AlterSchemaDataEvent;
7+
use Drupal\graphql\Event\AlterSchemaExtensionDataEvent;
8+
use Drupal\graphql\Plugin\SchemaExtensionPluginInterface;
9+
use GraphQL\Language\Parser;
10+
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
11+
use Drupal\Core\Extension\ModuleHandlerInterface;
12+
use Drupal\graphql\Plugin\SchemaExtensionPluginManager;
13+
use Symfony\Component\DependencyInjection\ContainerInterface;
14+
15+
/**
16+
* Allows to alter the graphql files data before parsing.
17+
*
18+
* @see \Drupal\graphql\Event\AlterSchemaDataEvent
19+
* @see \Drupal\graphql\Event\AlterSchemaExtensionDataEvent
20+
*
21+
* @Schema(
22+
* id = "alterable_composable",
23+
* name = "Alterable composable schema"
24+
* )
25+
*/
26+
class AlterableComposableSchema extends ComposableSchema {
27+
28+
/**
29+
* The event dispatcher service.
30+
*
31+
* @var \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher
32+
*/
33+
protected $dispatcher;
34+
35+
/**
36+
* {@inheritdoc}
37+
*
38+
* @codeCoverageIgnore
39+
*/
40+
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
41+
return new static(
42+
$configuration,
43+
$plugin_id,
44+
$plugin_definition,
45+
$container->get('cache.graphql.ast'),
46+
$container->get('module_handler'),
47+
$container->get('plugin.manager.graphql.schema_extension'),
48+
$container->getParameter('graphql.config'),
49+
$container->get('event_dispatcher')
50+
);
51+
}
52+
53+
/**
54+
* AlterableComposableSchema constructor.
55+
*
56+
* @param array $configuration
57+
* The plugin configuration array.
58+
* @param string $pluginId
59+
* The plugin id.
60+
* @param array $pluginDefinition
61+
* The plugin definition array.
62+
* @param \Drupal\Core\Cache\CacheBackendInterface $astCache
63+
* The cache bin for caching the parsed SDL.
64+
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
65+
* The module handler service.
66+
* @param \Drupal\graphql\Plugin\SchemaExtensionPluginManager $extensionManager
67+
* The schema extension plugin manager.
68+
* @param array $config
69+
* The service configuration.
70+
* @param \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher $dispatcher
71+
* The event dispatcher.
72+
*
73+
* @codeCoverageIgnore
74+
*/
75+
public function __construct(
76+
array $configuration,
77+
$pluginId,
78+
array $pluginDefinition,
79+
CacheBackendInterface $astCache,
80+
ModuleHandlerInterface $moduleHandler,
81+
SchemaExtensionPluginManager $extensionManager,
82+
array $config,
83+
ContainerAwareEventDispatcher $dispatcher
84+
) {
85+
parent::__construct(
86+
$configuration,
87+
$pluginId,
88+
$pluginDefinition,
89+
$astCache,
90+
$moduleHandler,
91+
$extensionManager,
92+
$config
93+
);
94+
$this->dispatcher = $dispatcher;
95+
}
96+
97+
/**
98+
* Retrieves the parsed AST of the schema definition.
99+
*
100+
* Almost copy of the original method except it
101+
* provides alter schema event in order to manipulate data.
102+
*
103+
* @param array $extensions
104+
* The Drupal GraphQl schema plugins data.
105+
*
106+
* @return \GraphQL\Language\AST\DocumentNode
107+
* The parsed schema document.
108+
*
109+
* @throws \GraphQL\Error\SyntaxError
110+
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
111+
*
112+
* @see \Drupal\graphql\Plugin\GraphQL\Schema\ComposableSchema::getSchemaDocument()
113+
*/
114+
protected function getSchemaDocument(array $extensions = []) {
115+
// Only use caching of the parsed document if we aren't in development mode.
116+
$cid = "schema:{$this->getPluginId()}";
117+
if (empty($this->inDevelopment) && $cache = $this->astCache->get($cid)) {
118+
return $cache->data;
119+
}
120+
121+
$extensions = array_filter(array_map(function (SchemaExtensionPluginInterface $extension) {
122+
return $extension->getBaseDefinition();
123+
}, $extensions), function ($definition) {
124+
return !empty($definition);
125+
});
126+
$schema = array_merge([$this->getSchemaDefinition()], $extensions);
127+
// Event in order to alter the schema data.
128+
$event = new AlterSchemaDataEvent($schema);
129+
$this->dispatcher->dispatch(
130+
$event,
131+
AlterSchemaDataEvent::EVENT_NAME
132+
);
133+
$ast = Parser::parse(implode("\n\n", $event->getSchemaData()));
134+
if (empty($this->inDevelopment)) {
135+
$this->astCache->set($cid, $ast, CacheBackendInterface::CACHE_PERMANENT, ['graphql']);
136+
}
137+
return $ast;
138+
}
139+
140+
/**
141+
* Retrieves the parsed AST of the schema extension definitions.
142+
*
143+
* Almost copy of the original method except it
144+
* provides alter schema extension event in order to manipulate data.
145+
*
146+
* @param array $extensions
147+
* The Drupal GraphQl extensions data.
148+
*
149+
* @return \GraphQL\Language\AST\DocumentNode|null
150+
* The parsed schema document.
151+
*
152+
* @throws \GraphQL\Error\SyntaxError
153+
*
154+
* @see \Drupal\graphql\Plugin\GraphQL\Schema\ComposableSchema::getSchemaDocument()
155+
*/
156+
protected function getExtensionDocument(array $extensions = []) {
157+
// Only use caching of the parsed document if we aren't in development mode.
158+
$cid = "extension:{$this->getPluginId()}";
159+
if (empty($this->inDevelopment) && $cache = $this->astCache->get($cid)) {
160+
return $cache->data;
161+
}
162+
163+
$extensions = array_filter(array_map(function (SchemaExtensionPluginInterface $extension) {
164+
return $extension->getExtensionDefinition();
165+
}, $extensions), function ($definition) {
166+
return !empty($definition);
167+
});
168+
169+
// Event in order to alter the schema extension data.
170+
$event = new AlterSchemaExtensionDataEvent($extensions);
171+
$this->dispatcher->dispatch(
172+
$event,
173+
AlterSchemaExtensionDataEvent::EVENT_NAME
174+
);
175+
$ast = !empty($extensions) ? Parser::parse(implode("\n\n", $event->getSchemaExtensionData())) : NULL;
176+
if (empty($this->inDevelopment)) {
177+
$this->astCache->set($cid, $ast, CacheBackendInterface::CACHE_PERMANENT, ['graphql']);
178+
}
179+
180+
return $ast;
181+
}
182+
183+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type: module
2+
name: GraphQL Alterable Schema Test
3+
description: Tests if alterting schema is working.
4+
package: Testing
5+
hidden: TRUE
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
services:
2+
3+
graphql_alterable_schema_test.alter_data_subscriber:
4+
class: Drupal\graphql_alterable_schema_test\EventSubscriber\GraphQlSubscriber
5+
tags:
6+
- { name: event_subscriber }
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Drupal\graphql_alterable_schema_test\EventSubscriber;
6+
7+
use Drupal\graphql\Event\AlterSchemaDataEvent;
8+
use Drupal\graphql\Event\AlterSchemaExtensionDataEvent;
9+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
10+
11+
/**
12+
* Subscribes to the graphql schema alter events.
13+
*/
14+
class GraphQlSubscriber implements EventSubscriberInterface {
15+
16+
/**
17+
* {@inheritdoc}
18+
*
19+
* @return array
20+
* The event names to listen for, and the methods that should be executed.
21+
*/
22+
public static function getSubscribedEvents() {
23+
return [
24+
AlterSchemaExtensionDataEvent::EVENT_NAME => ['alterSchemaExtensionData'],
25+
AlterSchemaDataEvent::EVENT_NAME => ['alterSchemaData'],
26+
];
27+
}
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function alterSchemaExtensionData(AlterSchemaExtensionDataEvent $event): void {
33+
$schemaData = $event->getSchemaExtensionData();
34+
// I do not recommend direct replace, better user parsing or regex.
35+
// But this is an example of what you can do.
36+
$schemaData['graphql_alterable_schema_test'] = str_replace('position: Int', 'position: Int!', $schemaData['graphql_alterable_schema_test']);
37+
$event->setSchemaExtensionData($schemaData);
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function alterSchemaData(AlterSchemaDataEvent $event): void {
44+
$schemaData = $event->getSchemaData();
45+
// It is not recommended direct replacement, better user parsing or regex.
46+
// But this is an example of what you can do.
47+
$schemaData[0] = str_replace('alterableQuery(id: Int): Result', 'alterableQuery(id: Int!): Result', $schemaData[0]);
48+
$event->setSchemaData($schemaData);
49+
}
50+
51+
}

0 commit comments

Comments
 (0)