Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added directives to extensions #1216

Open
wants to merge 6 commits into
base: 8.x-4.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/GraphQL/DirectiveProviderExtensionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Drupal\graphql\GraphQL;

/**
* Interface for Schema extensions that provide new directives.
*
* @package Drupal\graphql\GraphQL
*/
interface DirectiveProviderExtensionInterface {

/**
* Retrieve all directive definitions as a string.
*
* @return string
* Directive definitions in SDL.
*/
public function getDirectiveDefinitions() : string;

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

namespace Drupal\graphql\GraphQL;

use GraphQL\Language\AST\DocumentNode;

/**
* Interface for schema extensions that need to inspect the host schema.
*
* @package Drupal\graphql\GraphQL
*/
interface ParentAwareSchemaExtensionInterface {

/**
* Pass the parent schema document to the extension.
*
* @param \GraphQL\Language\AST\DocumentNode $document
* The parent schema document.
*/
public function setParentSchemaDocument(DocumentNode $document): void;

}
47 changes: 42 additions & 5 deletions src/Plugin/GraphQL/Schema/ComposableSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\graphql\GraphQL\DirectiveProviderExtensionInterface;
use Drupal\graphql\GraphQL\ParentAwareSchemaExtensionInterface;
use Drupal\graphql\GraphQL\ResolverRegistry;
use Drupal\Core\StringTranslation\StringTranslationTrait;

Expand All @@ -29,23 +31,58 @@ public function getResolverRegistry() {
* {@inheritdoc}
*/
protected function getExtensions() {
return array_map(function ($id) {
$extensions = array_map(function ($id) {
return $this->extensionManager->createInstance($id);
}, array_filter($this->getConfiguration()['extensions']));

$schemaDocument = $this->getSchemaDocument($extensions);
// Iterate through all extensions and pass them the current schema, so they
// can act on it.
foreach ($extensions as $extension) {
if ($extension instanceof ParentAwareSchemaExtensionInterface) {
$extension->setParentSchemaDocument($schemaDocument);
}
}

return $extensions;
}

/**
* {@inheritdoc}
*/
protected function getSchemaDefinition() {
return <<<GQL
type Schema {
query: Query
$extensions = parent::getExtensions();

// Get all extensions and prepend any defined directives to the schema.
$schema = [];
foreach ($extensions as $extension) {
if ($extension instanceof DirectiveProviderExtensionInterface) {
$schema[] = $extension->getDirectiveDefinitions();
}
}

type Query
// Attempt to load a schema file and return it instead of the hardcoded
// empty schema.
$id = $this->getPluginId();
$definition = $this->getPluginDefinition();
$module = $this->moduleHandler->getModule($definition['provider']);
$path = 'graphql/' . $id . '.graphqls';
$file = $module->getPath() . '/' . $path;

if (!file_exists($file)) {
$schema[] = <<<GQL
type Schema {
query: Query
}

type Query
GQL;
}
else {
$schema[] = file_get_contents($file);
}

return implode("\n", $schema);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type Car @dimensions(includeDepth: true) {
brand: String!
model: String!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extend type Query {
cars: [Car]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: Graphql Extension Directives Test
type: module
description: 'Test module for directives provided by extensions.'
package: Testing
core_version_requirement: ^8 || ^9
dependencies:
- graphql:graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

namespace Drupal\graphql_extension_directives_test\Plugin\GraphQL\SchemaExtension;

use Drupal\graphql\GraphQL\DirectiveProviderExtensionInterface;
use Drupal\graphql\GraphQL\ParentAwareSchemaExtensionInterface;
use Drupal\graphql\GraphQL\ResolverBuilder;
use Drupal\graphql\GraphQL\ResolverRegistryInterface;
use Drupal\graphql\Plugin\GraphQL\SchemaExtension\SdlSchemaExtensionPluginBase;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;

/**
* Schema extension plugin.
*
* @SchemaExtension(
* id = "extension_directives_test",
* name = "Extension Directives Test",
* description = "Provides resolvers for testing extension directives.",
* schema = "composable"
* )
*/
class ExtensionDirectivesTestSchemaExtension extends SdlSchemaExtensionPluginBase implements ParentAwareSchemaExtensionInterface, DirectiveProviderExtensionInterface {

/**
* The parent schema's AST.
*
* @var \GraphQL\Language\AST\DocumentNode
*/
protected $parentAst;

/**
* Stores found types with the dimensions directive.
*
* @var array
*/
protected $typesWithDimensions = [];

/**
* {@inheritDoc}
*/
public function setParentSchemaDocument($document): void {
$this->parentAst = $document;
}

/**
* {@inheritDoc}
*/
public function getDirectiveDefinitions(): string {
return <<<GQL
directive @dimensions(
includeDepth: Boolean!
) on OBJECT
GQL;
}

/**
* {@inheritDoc}
*/
public function getExtensionDefinition() {
$schema = [];
$typesWithDimensions = $this->getTypesWithDimensions();

foreach ($typesWithDimensions as $typeWithDimensions) {
$schema[] = "extend type $typeWithDimensions[type_name] {";
$schema[] = " width: String!";
$schema[] = " height: String!";
if (isset($typeWithDimensions['args']['includeDepth']) && $typeWithDimensions['args']['includeDepth']) {
$schema[] = " depth: String!";
}
$schema[] = "}";
}

array_unshift($schema, parent::getExtensionDefinition());
return implode("\n", $schema);
}

/**
* {@inheritDoc}
*/
public function registerResolvers(ResolverRegistryInterface $registry): void {
$builder = new ResolverBuilder();
$registry->addFieldResolver('Query', 'cars', $builder->callback(function () {
return [(object) ['brand' => 'Brand', 'model' => 'Model']];
}));
foreach ($this->getTypesWithDimensions() as $typeWithDimensions) {
$registry->addFieldResolver($typeWithDimensions['type_name'], 'width', $builder->callback(function () {
return '1';
}));
$registry->addFieldResolver($typeWithDimensions['type_name'], 'height', $builder->callback(function () {
return '1';
}));
if (isset($typeWithDimensions['args']['includeDepth']) && $typeWithDimensions['args']['includeDepth']) {
$registry->addFieldResolver($typeWithDimensions['type_name'], 'depth', $builder->callback(function () {
return '1';
}));
}
}
}

/**
* Retrieve all directive calls in the host schema.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
public function getTypesWithDimensions(): array {
if (count($this->typesWithDimensions) === 0) {
// Search for object type definitions ...
foreach ($this->parentAst->definitions->getIterator() as $definition) {
// ... that have directives.
if ($definition instanceof ObjectTypeDefinitionNode) {
foreach ($definition->directives->getIterator() as $directive) {
/** @var \GraphQL\Language\AST\DirectiveNode $directive */
$directiveName = $directive->name->value;
if ($directiveName != 'dimensions') {
continue;
}
$typeName = $definition->name->value;
$args = [];
foreach ($directive->arguments->getIterator() as $arg) {
/** @var \GraphQL\Language\AST\ArgumentNode $arg */
$args[$arg->name->value] = $arg->value->value;
}
$this->typesWithDimensions[] = [
'directive_name' => $directiveName,
'type_name' => $typeName,
'args' => $args,
];
}
}
}
}
return $this->typesWithDimensions;
}

}
77 changes: 77 additions & 0 deletions tests/src/Kernel/ExtensionDirectivesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Drupal\Tests\graphql\Kernel;

/**
* Test the entity_definition data producer and friends.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong comment?

*
* @group graphql
*/
class ExtensionDirectivesTest extends GraphQLTestBase {

/**
* {@inheritdoc}
*/
protected $strictConfigSchema = FALSE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why we disable that? Please add a code comment.


/**
* {@inheritdoc}
*/
protected static $modules = [
'graphql_extension_directives_test',
];

/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();

$this->createTestServer(
'composable',
'/extension-directives-test',
[
'schema_configuration' => [
'composable' => [
'extensions' => [
'extension_directives_test' => 'extension_directives_test',
],
],
],
]
);
}

/**
* Tests that retrieving an entity definition works.
*/
public function testFields(): void {
$query = <<<GQL
query {
cars {
brand
model
width
height
depth
}
}
GQL;

$this->assertResults($query, [],
[
'cars' =>
[
[
'brand' => 'Brand',
'model' => 'Model',
'width' => '1',
'height' => '1',
'depth' => '1',
],
],
]
);
}

}