Skip to content

Commit

Permalink
Pulls/master/ extendsive refactor (#393)
Browse files Browse the repository at this point in the history
* First cut

* Apply to queries, fix inheritance bugs

* Tests passing

* Initial fixes for decoupled

* Numerous updates to union/interface generation to support Gatsby

* Getting there

* Back to schema generation in gatsby

* Broke up Inheritance into separate services

* Unit tests for new inheritance

* Tests passing

* Rename to SchemaConfigProvider

* Linting

* Add inheritance check for field mapping

* Use correct event name, no dot syntax

* Use unions on single results, e.g. has_one

* NEW: Refactor to use interface queries (non-breaking)

* Get tests passing

* Allow type eager loading for types that don't appear in queries

* Simplify exists() check so it doesn't create a new schema every time

* Ensure method exists

* MINOR: firstResult typed to SS_List for non-dataobject cases

* Remove API change to SchemaUpdater

* Linting

* Fix method return type

* Fix Enum overwriting local variable and breaking enumerated arrays

* Add base resolver, allow native field to resolve

* Change inheritance to apply on models, instead of updateSchema

* Fix tests

* Linting

* Ensure inheritance follows versioned, include bespoke types in query collector, add hiddenAncestor

* Cache query collection for performance

Co-authored-by: Aaron Carlino <[email protected]>
  • Loading branch information
Aaron Carlino and Aaron Carlino committed Jun 30, 2021
1 parent 841c4e2 commit cf9c875
Show file tree
Hide file tree
Showing 75 changed files with 2,654 additions and 398 deletions.
1 change: 0 additions & 1 deletion _config/dataobject.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ Name: silverstripe-graphql-dataobject
---
SilverStripe\ORM\DataObject:
graphql_blacklisted_fields:
ClassName: true
LinkTracking: true
FileTracking: true
extensions:
Expand Down
4 changes: 2 additions & 2 deletions _config/dbtypes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ SilverStripe\ORM\FieldType\DBFloat:
SilverStripe\ORM\FieldType\DBDecimal:
graphql_type: Float
SilverStripe\ORM\FieldType\DBPrimaryKey:
graphql_type: ID
graphql_type: ID!
SilverStripe\ORM\FieldType\DBForeignKey:
graphql_type: ID
graphql_type: ID!
13 changes: 12 additions & 1 deletion _config/schema-global.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Name: 'graphql-schema-global'
SilverStripe\GraphQL\Schema\Schema:
schemas:
'*':
scalars:
JSONBlob:
serialiser: 'SilverStripe\GraphQL\Schema\Resolver\JSONResolver::serialise'
valueParser: 'SilverStripe\GraphQL\Schema\Resolver\JSONResolver::parseValue'
literalParser: 'SilverStripe\GraphQL\Schema\Resolver\JSONResolver::parseLiteral'
config:
resolverStrategy: 'SilverStripe\GraphQL\Schema\Resolver\DefaultResolverStrategy::getResolverMethod'
defaultResolver: 'SilverStripe\GraphQL\Schema\Resolver\DefaultResolver::defaultFieldResolver'
Expand All @@ -14,8 +19,14 @@ SilverStripe\GraphQL\Schema\Schema:
type_formatter: 'SilverStripe\Core\ClassInfo::shortName'
type_prefix: ''
type_mapping: []
base_fields:
ID: ID
plugins:
inheritance: true
inheritance:
useUnionQueries: false
hideAncestors:
- SilverStripe\CMS\Model\SiteTree
after: 'versioning'
inheritedPlugins:
after: '*'
operations:
Expand Down
38 changes: 34 additions & 4 deletions src/Config/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ public function get($path, $default = null)

/**
* @param $path
* @param $value
* @param callable $callback
* @return $this
* @throws SchemaBuilderException
*/
public function set($path, $value): self
private function path($path, $callback): void
{
if (is_string($path)) {
$path = explode('.', $path);
Expand All @@ -72,8 +72,8 @@ public function set($path, $value): self
foreach ($path as $i => $part) {
$last = ($i + 1) === sizeof($path);
if ($last) {
$scope[$part] = $value;
return $this;
$callback($scope, $part);
return;
}
if (!isset($scope[$part])) {
$scope[$part] = [];
Expand All @@ -82,6 +82,36 @@ public function set($path, $value): self
}
}

/**
* @param $path
* @param $value
* @return $this
* @throws SchemaBuilderException
*/
public function set($path, $value): self
{
$this->path($path, function (&$scope, $part) use ($value) {
$scope[$part] = $value;
});

return $this;
}

/**
* @param $path
* @param $value
* @return $this
* @throws SchemaBuilderException
*/
public function unset($path): self
{
$this->path($path, function (&$scope, $part) {
unset($scope[$part]);
});

return $this;
}

/**
* @param array $settings
* @return $this
Expand Down
20 changes: 20 additions & 0 deletions src/Config/ModelConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ public function getTypeName(string $class): string
return $prefix . $typeName;
}

/**
* Fields that are added to the model by default. Can be opted out per type
* @return array
* @throws SchemaBuilderException
*/
public function getDefaultFields(): array
{
return $this->get('default_fields', []);
}

/**
* Fields that will appear on all models. Cannot be opted out on any type.
* @return array
* @throws SchemaBuilderException
*/
public function getBaseFields(): array
{
return $this->get('base_fields', []);
}

/**
* @param string $class
* @return string
Expand Down
27 changes: 16 additions & 11 deletions src/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace SilverStripe\GraphQL;

use Exception;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use InvalidArgumentException;
use LogicException;
use SilverStripe\Control\Controller as BaseController;
Expand All @@ -15,13 +17,14 @@
use SilverStripe\EventDispatcher\Symfony\Event;
use SilverStripe\GraphQL\Auth\Handler;
use SilverStripe\GraphQL\PersistedQuery\RequestProcessor;
use SilverStripe\GraphQL\QueryHandler\QueryHandler;
use SilverStripe\GraphQL\QueryHandler\QueryHandlerInterface;
use SilverStripe\GraphQL\QueryHandler\QueryStateProvider;
use SilverStripe\GraphQL\Schema\Exception\SchemaBuilderException;
use SilverStripe\GraphQL\QueryHandler\RequestContextProvider;
use SilverStripe\GraphQL\QueryHandler\SchemaContextProvider;
use SilverStripe\GraphQL\QueryHandler\SchemaConfigProvider;
use SilverStripe\GraphQL\QueryHandler\TokenContextProvider;
use SilverStripe\GraphQL\QueryHandler\UserContextProvider;
use SilverStripe\GraphQL\Schema\Schema;
use SilverStripe\GraphQL\Schema\SchemaBuilder;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
Expand Down Expand Up @@ -128,21 +131,22 @@ public function index(HTTPRequest $request): HTTPResponse
}
$handler = $this->getQueryHandler();
$this->applyContext($handler);

$queryDocument = Parser::parse(new Source($query));
$ctx = $handler->getContext();
$result = $handler->query($graphqlSchema, $query, $variables);

// Fire an eventYou
$eventContext = [
'schema' => $graphqlSchema,
'schemaKey' => $this->getSchemaKey(),
'query' => $query,
'context' => $ctx,
'variables' => $variables,
'result' => $result,
];

Dispatcher::singleton()->trigger('onGraphQLQuery', Event::create($this->getSchemaKey(), $eventContext));

$result = $handler->query($graphqlSchema, $query, $variables);
$eventContext['result'] = $result;

Dispatcher::singleton()->trigger('onGraphQLResponse', Event::create($this->getSchemaKey(), $eventContext));
$event = QueryHandler::isMutation($query) ? 'graphqlMutation' : 'graphqlQuery';
$operationName = QueryHandler::getOperationName($queryDocument);
Dispatcher::singleton()->trigger($event, Event::create($operationName, $eventContext));
} catch (Exception $exception) {
$error = ['message' => $exception->getMessage()];

Expand Down Expand Up @@ -305,8 +309,9 @@ protected function applyContext(QueryHandlerInterface $handler)
->addContextProvider(RequestContextProvider::create($request));
$schemaContext = SchemaBuilder::singleton()->getConfig($this->getSchemaKey());
if ($schemaContext) {
$handler->addContextProvider(SchemaContextProvider::create($schemaContext));
$handler->addContextProvider(SchemaConfigProvider::create($schemaContext));
}
$handler->addContextProvider(QueryStateProvider::create());
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/Dev/Build.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use SilverStripe\GraphQL\Schema\Exception\SchemaNotFoundException;
use SilverStripe\GraphQL\Schema\Schema;
use SilverStripe\GraphQL\Schema\SchemaBuilder;
use SilverStripe\ORM\DatabaseAdmin;

class Build extends Controller
{
Expand Down
59 changes: 44 additions & 15 deletions src/QueryHandler/QueryHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use GraphQL\Error\SyntaxError;
use GraphQL\Executor\ExecutionResult;
use GraphQL\GraphQL;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
Expand All @@ -22,6 +23,7 @@
use SilverStripe\GraphQL\PersistedQuery\PersistedQueryMappingProvider;
use SilverStripe\GraphQL\PersistedQuery\PersistedQueryProvider;
use SilverStripe\GraphQL\Schema\Interfaces\ContextProvider;
use SilverStripe\GraphQL\Schema\Schema;
use SilverStripe\ORM\ValidationException;

/**
Expand Down Expand Up @@ -69,11 +71,11 @@ public function __construct(array $contextProviders = [])

/**
* @param GraphQLSchema $schema
* @param string $query
* @param string|DocumentNode $query
* @param array|null $vars
* @return array
*/
public function query(GraphQLSchema $schema, string $query, ?array $vars = []): array
public function query(GraphQLSchema $schema, $query, ?array $vars = []): array
{
$executionResult = $this->queryAndReturnResult($schema, $query, $vars);

Expand All @@ -86,11 +88,11 @@ public function query(GraphQLSchema $schema, string $query, ?array $vars = []):

/**
* @param GraphQLSchema $schema
* @param string $query
* @param string|DocumentNode $query
* @param array|null $vars
* @return array|ExecutionResult
*/
public function queryAndReturnResult(GraphQLSchema $schema, string $query, ?array $vars = [])
public function queryAndReturnResult(GraphQLSchema $schema, $query, ?array $vars = [])
{
$context = $this->getContext();
$last = function ($schema, $query, $context, $vars) {
Expand Down Expand Up @@ -204,16 +206,6 @@ public function addMiddleware(QueryMiddleware $middleware): self
return $this;
}

/**
* @return array
*/
protected function getContextDefaults(): array
{
return [
self::CURRENT_USER => $this->getMemberContext(),
];
}

/**
* Call middleware to evaluate a graphql query
*
Expand Down Expand Up @@ -296,7 +288,7 @@ public static function isMutation(string $query): bool
}

// Otherwise, bring in the big guns.
$document = Parser::parse(new Source($query ?: 'GraphQL'));
$document = Parser::parse(new Source($query));
$defs = $document->definitions;
foreach ($defs as $statement) {
$options = [
Expand All @@ -313,4 +305,41 @@ public static function isMutation(string $query): bool

return false;
}

/**
* @param DocumentNode $document
* @param bool $preferStatementName If true, use query <MyQueryName(Vars..)>. If false, use the actual field name.
* @return string
*/
public static function getOperationName(DocumentNode $document, bool $preferStatementName = true): string
{
$defs = $document->definitions;
foreach ($defs as $statement) {
$options = [
NodeKind::OPERATION_DEFINITION,
NodeKind::OPERATION_TYPE_DEFINITION
];
if (!in_array($statement->kind, $options, true)) {
continue;
}
if (in_array($statement->operation, ['query', 'mutation'])) {
// If the operation was given a name, use that
$name = $statement->name;
if ($name && $name->value && $preferStatementName) {
return $name->value;
}
$selectionSet = $statement->selectionSet;
if ($selectionSet) {
$selections = $selectionSet->selections;
if (!empty($selections)) {
$firstField = $selections[0];

return $firstField->name->value;
}
}
return $statement->operation;
}
}
return 'graphql';
}
}
9 changes: 8 additions & 1 deletion src/QueryHandler/QueryHandlerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace SilverStripe\GraphQL\QueryHandler;

use GraphQL\Executor\ExecutionResult;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Type\Schema;
use SilverStripe\GraphQL\Schema\Interfaces\ContextProvider;

Expand All @@ -13,7 +14,13 @@
*/
interface QueryHandlerInterface
{
public function query(Schema $schema, string $query, array $params = []): array;
/**
* @param Schema $schema
* @param string|DocumentNode $query
* @param array $params
* @return array
*/
public function query(Schema $schema, $query, array $params = []): array;

/**
* Serialise a Graphql result object for output
Expand Down
52 changes: 52 additions & 0 deletions src/QueryHandler/QueryStateProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php


namespace SilverStripe\GraphQL\QueryHandler;

use SilverStripe\Core\Injector\Injectable;
use SilverStripe\GraphQL\Config\Configuration;
use SilverStripe\GraphQL\Schema\Interfaces\ContextProvider;

/**
* Provides an arbitrary state container that can be passed through
* the resolver chain. It is empty by default and derives
* no state from the actual schema
*/
class QueryStateProvider implements ContextProvider
{
use Injectable;

const KEY = 'queryState';

/**
* @var Configuration
*/
private $queryState;

/**
* QueryStateProvider constructor.
*/
public function __construct()
{
$this->queryState = new Configuration();
}

/**
* @param array $context
* @return mixed|null
*/
public static function get(array $context): Configuration
{
return $context[self::KEY] ?? new Configuration();
}

/**
* @return array[]
*/
public function provideContext(): array
{
return [
self::KEY => $this->queryState,
];
}
}
Loading

0 comments on commit cf9c875

Please sign in to comment.