Skip to content

Commit

Permalink
feat(schema): Allow altering of schemas by introducing alter events i…
Browse files Browse the repository at this point in the history
…n a new base class (#1301) (#1302)
  • Loading branch information
akhomy authored Oct 5, 2022
1 parent 3be06ac commit 4f92c53
Show file tree
Hide file tree
Showing 8 changed files with 555 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/Event/AlterSchemaDataEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types = 1);

namespace Drupal\graphql\Event;

use Drupal\Component\EventDispatcher\Event;

/**
* Represents an event that is triggered to alter schema data.
*/
class AlterSchemaDataEvent extends Event {

/**
* Event fired to alter schema data.
*
* @var string
*/
const EVENT_NAME = 'graphql.sdl.alter_schema';

/**
* The schema array data.
*
* @var array
*/
protected $schemaData;

/**
* AlterSchemaDataEvent constructor.
*
* @param array $schemaData
* The schema data reference.
*/
public function __construct(array &$schemaData) {
$this->schemaData = $schemaData;
}

/**
* Returns the schema data.
*
* @return array
* The schema data.
*/
public function getSchemaData(): array {
return $this->schemaData;
}

/**
* Sets the schema data.
*
* @param array $schemaData
* The schema data.
*/
public function setSchemaData(array $schemaData): void {
$this->schemaData = $schemaData;
}

}
58 changes: 58 additions & 0 deletions src/Event/AlterSchemaExtensionDataEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types = 1);

namespace Drupal\graphql\Event;

use Drupal\Component\EventDispatcher\Event;

/**
* Represents an event that is triggered to alter schema extension data.
*/
class AlterSchemaExtensionDataEvent extends Event {

/**
* Event fired to alter schema extension data.
*
* @var string
*/
const EVENT_NAME = 'graphql.sdl.alter_schema_extension';

/**
* The schema array data.
*
* @var array
*/
protected $schemaExtensionData;

/**
* AlterSchemaExtensionDataEvent constructor.
*
* @param array $schemaExtensionData
* The schema extension data.
*/
public function __construct(array $schemaExtensionData) {
$this->schemaExtensionData = $schemaExtensionData;
}

/**
* Returns the schema extension data.
*
* @return array
* The schema extension data.
*/
public function getSchemaExtensionData(): array {
return $this->schemaExtensionData;
}

/**
* Returns the schema extension data.
*
* @param array $schemaExtensionData
* The schema extension data.
*/
public function setSchemaExtensionData(array $schemaExtensionData): void {
$this->schemaExtensionData = $schemaExtensionData;
}

}
183 changes: 183 additions & 0 deletions src/Plugin/GraphQL/Schema/AlterableComposableSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php

namespace Drupal\graphql\Plugin\GraphQL\Schema;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\graphql\Event\AlterSchemaDataEvent;
use Drupal\graphql\Event\AlterSchemaExtensionDataEvent;
use Drupal\graphql\Plugin\SchemaExtensionPluginInterface;
use GraphQL\Language\Parser;
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\graphql\Plugin\SchemaExtensionPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Allows to alter the graphql files data before parsing.
*
* @see \Drupal\graphql\Event\AlterSchemaDataEvent
* @see \Drupal\graphql\Event\AlterSchemaExtensionDataEvent
*
* @Schema(
* id = "alterable_composable",
* name = "Alterable composable schema"
* )
*/
class AlterableComposableSchema extends ComposableSchema {

/**
* The event dispatcher service.
*
* @var \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher
*/
protected $dispatcher;

/**
* {@inheritdoc}
*
* @codeCoverageIgnore
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('cache.graphql.ast'),
$container->get('module_handler'),
$container->get('plugin.manager.graphql.schema_extension'),
$container->getParameter('graphql.config'),
$container->get('event_dispatcher')
);
}

/**
* AlterableComposableSchema constructor.
*
* @param array $configuration
* The plugin configuration array.
* @param string $pluginId
* The plugin id.
* @param array $pluginDefinition
* The plugin definition array.
* @param \Drupal\Core\Cache\CacheBackendInterface $astCache
* The cache bin for caching the parsed SDL.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* The module handler service.
* @param \Drupal\graphql\Plugin\SchemaExtensionPluginManager $extensionManager
* The schema extension plugin manager.
* @param array $config
* The service configuration.
* @param \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher $dispatcher
* The event dispatcher.
*
* @codeCoverageIgnore
*/
public function __construct(
array $configuration,
$pluginId,
array $pluginDefinition,
CacheBackendInterface $astCache,
ModuleHandlerInterface $moduleHandler,
SchemaExtensionPluginManager $extensionManager,
array $config,
ContainerAwareEventDispatcher $dispatcher
) {
parent::__construct(
$configuration,
$pluginId,
$pluginDefinition,
$astCache,
$moduleHandler,
$extensionManager,
$config
);
$this->dispatcher = $dispatcher;
}

/**
* Retrieves the parsed AST of the schema definition.
*
* Almost copy of the original method except it
* provides alter schema event in order to manipulate data.
*
* @param array $extensions
* The Drupal GraphQl schema plugins data.
*
* @return \GraphQL\Language\AST\DocumentNode
* The parsed schema document.
*
* @throws \GraphQL\Error\SyntaxError
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*
* @see \Drupal\graphql\Plugin\GraphQL\Schema\ComposableSchema::getSchemaDocument()
*/
protected function getSchemaDocument(array $extensions = []) {
// Only use caching of the parsed document if we aren't in development mode.
$cid = "schema:{$this->getPluginId()}";
if (empty($this->inDevelopment) && $cache = $this->astCache->get($cid)) {
return $cache->data;
}

$extensions = array_filter(array_map(function (SchemaExtensionPluginInterface $extension) {
return $extension->getBaseDefinition();
}, $extensions), function ($definition) {
return !empty($definition);
});
$schema = array_merge([$this->getSchemaDefinition()], $extensions);
// Event in order to alter the schema data.
$event = new AlterSchemaDataEvent($schema);
$this->dispatcher->dispatch(
$event,
AlterSchemaDataEvent::EVENT_NAME
);
$ast = Parser::parse(implode("\n\n", $event->getSchemaData()));
if (empty($this->inDevelopment)) {
$this->astCache->set($cid, $ast, CacheBackendInterface::CACHE_PERMANENT, ['graphql']);
}
return $ast;
}

/**
* Retrieves the parsed AST of the schema extension definitions.
*
* Almost copy of the original method except it
* provides alter schema extension event in order to manipulate data.
*
* @param array $extensions
* The Drupal GraphQl extensions data.
*
* @return \GraphQL\Language\AST\DocumentNode|null
* The parsed schema document.
*
* @throws \GraphQL\Error\SyntaxError
*
* @see \Drupal\graphql\Plugin\GraphQL\Schema\ComposableSchema::getSchemaDocument()
*/
protected function getExtensionDocument(array $extensions = []) {
// Only use caching of the parsed document if we aren't in development mode.
$cid = "extension:{$this->getPluginId()}";
if (empty($this->inDevelopment) && $cache = $this->astCache->get($cid)) {
return $cache->data;
}

$extensions = array_filter(array_map(function (SchemaExtensionPluginInterface $extension) {
return $extension->getExtensionDefinition();
}, $extensions), function ($definition) {
return !empty($definition);
});

// Event in order to alter the schema extension data.
$event = new AlterSchemaExtensionDataEvent($extensions);
$this->dispatcher->dispatch(
$event,
AlterSchemaExtensionDataEvent::EVENT_NAME
);
$ast = !empty($extensions) ? Parser::parse(implode("\n\n", $event->getSchemaExtensionData())) : NULL;
if (empty($this->inDevelopment)) {
$this->astCache->set($cid, $ast, CacheBackendInterface::CACHE_PERMANENT, ['graphql']);
}

return $ast;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: module
name: GraphQL Alterable Schema Test
description: Tests if alterting schema is working.
package: Testing
hidden: TRUE
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
services:

graphql_alterable_schema_test.alter_data_subscriber:
class: Drupal\graphql_alterable_schema_test\EventSubscriber\GraphQlSubscriber
tags:
- { name: event_subscriber }
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types = 1);

namespace Drupal\graphql_alterable_schema_test\EventSubscriber;

use Drupal\graphql\Event\AlterSchemaDataEvent;
use Drupal\graphql\Event\AlterSchemaExtensionDataEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Subscribes to the graphql schema alter events.
*/
class GraphQlSubscriber implements EventSubscriberInterface {

/**
* {@inheritdoc}
*
* @return array
* The event names to listen for, and the methods that should be executed.
*/
public static function getSubscribedEvents() {
return [
AlterSchemaExtensionDataEvent::EVENT_NAME => ['alterSchemaExtensionData'],
AlterSchemaDataEvent::EVENT_NAME => ['alterSchemaData'],
];
}

/**
* {@inheritdoc}
*/
public function alterSchemaExtensionData(AlterSchemaExtensionDataEvent $event): void {
$schemaData = $event->getSchemaExtensionData();
// I do not recommend direct replace, better user parsing or regex.
// But this is an example of what you can do.
$schemaData['graphql_alterable_schema_test'] = str_replace('position: Int', 'position: Int!', $schemaData['graphql_alterable_schema_test']);
$event->setSchemaExtensionData($schemaData);
}

/**
* {@inheritdoc}
*/
public function alterSchemaData(AlterSchemaDataEvent $event): void {
$schemaData = $event->getSchemaData();
// It is not recommended direct replacement, better user parsing or regex.
// But this is an example of what you can do.
$schemaData[0] = str_replace('alterableQuery(id: Int): Result', 'alterableQuery(id: Int!): Result', $schemaData[0]);
$event->setSchemaData($schemaData);
}

}
Loading

0 comments on commit 4f92c53

Please sign in to comment.