Documented lazy loading of types in schema and ability to define schema using GraphQL type language

This commit is contained in:
Vladimir Razuvaev 2017-08-17 20:35:35 +07:00
parent e52fe8c384
commit c65d8d8624
2 changed files with 167 additions and 0 deletions

View File

@ -88,3 +88,84 @@ mutation | `ObjectType` | Object type (usually named "Mutation") containing
subscription | `ObjectType` | Reserved for future subscriptions implementation. Currently presented for compatibility with introspection query of **graphql-js**, used by various clients (like Relay or GraphiQL)
directives | `Directive[]` | Full list of [directives](directives/) supported by your schema. By default contains built-in `@skip` and `@include` directives.<br><br> If you pass your own directives and still want to use built-in directives - add them explicitly. For example: `array_merge(GraphQL::getInternalDirectives(), [$myCustomDirective]`
types | `ObjectType[]` | List of object types which cannot be detected by **graphql-php** during static schema analysis.<br><br>Most often it happens when object type is never referenced in fields directly, but is still a part of schema because it implements an interface which resolves to this object type in it's `resolveType` callback. <br><br> Note that you are not required to pass all of your types here - it is simply a workaround for concrete use-case.
# Lazy loading of types
By default a schema will scan all of your type and field definitions to serve GraphQL queries.
It may cause performance overhead when there are many types in a schema.
In this case it is recommended to pass **typeLoader** option to schema constructor and define all
of your object **fields** as callbacks.
Type loading concept is very similar to PHP class loading, but keep in mind that **typeLoader** must
always return the same instance of a type.
Usage example:
```php
class Types
{
private $registry = [];
public function get($name)
{
if (!isset($this->types[$name])) {
$this->types[$name] = $this->{$name}();
}
return $this->types[$name];
}
private function MyTypeA()
{
return new ObjectType([
'name' => 'MyTypeA',
'fields' => function() {
return [
'b' => ['type' => $this->get('MyTypeB')]
];
}
]);
}
private function MyTypeB()
{
// ...
}
}
$registry = new Types();
$schema = new Schema([
'query' => $registry->get('Query'),
'typeLoader' => function($name) use ($registry) {
return $registry->get($name);
}
]);
```
# Schema Validation
By default schema is created with only shallow validation of type and field definitions
(because validation requires full schema scan and is very costly on bigger schemas).
But there is a special method `$schema->assertValid()` which throws `GraphQL\Error\InvariantViolation`
exception when it encounters any error, like:
- Invalid types used for fields / arguments
- Missing interface implementations
- Invalid interface implementations
- Other schema errors...
Schema validation is supposed to be used in CLI commands or during build step of your app.
Don't call it in web requests in production.
Usage example:
```php
$schema = new GraphQL\Type\Schema([
'query' => $myQueryType
]);
try {
$schema->assertValid();
} catch (GraphQL\Error\InvariantViolation $e) {
echo $e->getMessage();
}
```

View File

@ -0,0 +1,86 @@
# Defining your schema
[Type language](http://graphql.org/learn/schema/#type-language) is a convenient way to define your schema,
especially with IDE autocompletion and syntax validation.
Here is a simple schema defined in GraphQL type language (e.g. in separate **schema.graphql** file):
```graphql
schema {
query: Query
mutation: Mutation
}
type Query {
greetings(input: HelloInput!): String!
}
input HelloInput {
firstName: String!
lastName: String
}
```
In order to create schema instance out of this file, use `GraphQL\Utils\BuildSchema`:
```php
<?php
use GraphQL\Utils\BuildSchema;
$contents = file_get_contents('schema.graphql');
$schema = BuildSchema::build($contents);
```
By default such schema is created without any resolvers. As a result it doesn't support **Interfaces** and **Unions**
because it is impossible to resolve actual implementations during execution.
Also we have to rely on [default field resolver](/data-fetching/#default-field-resolver) and **root value** in
order to execute query against this schema.
# Defining resolvers
In order to enable **Interfaces**, **Unions** and custom field resolvers you can pass second argument:
**type config decorator** to schema builder.
It accepts default type config produced by builder and is expected to add missing options like
[`resolveType`](/type-system/interfaces/#configuration-options) for interface types or
[`resolveField`](/type-system/object-types/#configuration-options) for object types.
```php
<?php
use GraphQL\Utils\BuildSchema;
$typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
$name = $typeConfig['name'];
// ... add missing options to $typeConfig based on type $name
return $typeConfig;
};
$contents = file_get_contents('schema.graphql');
$schema = BuildSchema::build($contents, $typeConfigDecorator);
```
# Performance considerations
Method `BuildSchema::build()` produces a [lazy schema](/type-system/schema/#lazy-loading-of-types)
automatically, so it works efficiently even with very large schemas.
But parsing type definition file on each request is suboptimal, so it is recommended to cache
intermediate parsed representation of the schema for production environment:
```php
<?php
use GraphQL\Language\Parser;
use GraphQL\Utils\BuildSchema;
use GraphQL\Language\AST\Node;
$cacheFilename = 'cached_schema.php';
if (!file_exists($cacheFilename)) {
$document = Parser::parse(file_get_contents('./schema.graphql'));
file_put_contents($cacheFilename, "<?php\nreturn " . var_export($document->toArray(), true));
} else {
$document = Node::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
}
$typeConfigDecorator = function () {};
$schema = BuildSchema::build($document, $typeConfigDecorator);
```