From e69667c6335945ebeb8064260cddd012f91b93a1 Mon Sep 17 00:00:00 2001 From: vladar Date: Sat, 5 Nov 2016 23:55:51 +0700 Subject: [PATCH] Work in progress on better docs --- docs/best-practices.md | 19 +++ docs/complementary-tools.md | 3 + docs/{overview.md => concepts.md} | 70 +++++++-- docs/data-fetching.md | 0 docs/getting-started.md | 90 ++---------- docs/index.md | 57 ++------ docs/type-system.md | 1 - docs/type-system/abstract-types.md | 36 +++++ docs/type-system/enum-types.md | 180 +++++++++++++++++++++++ docs/type-system/index.md | 158 +++++++++++++++++++++ docs/type-system/lists-and-nonnulls.md | 61 ++++++++ docs/type-system/non-null.md | 0 docs/type-system/object-types.md | 188 +++++++++++++++++++++++++ docs/type-system/scalar-types.md | 148 +++++++++++++++++++ docs/type-system/schema.md | 0 mkdocs.yml | 13 +- 16 files changed, 884 insertions(+), 140 deletions(-) create mode 100644 docs/best-practices.md create mode 100644 docs/complementary-tools.md rename docs/{overview.md => concepts.md} (60%) create mode 100644 docs/data-fetching.md delete mode 100644 docs/type-system.md create mode 100644 docs/type-system/abstract-types.md create mode 100644 docs/type-system/enum-types.md create mode 100644 docs/type-system/index.md create mode 100644 docs/type-system/lists-and-nonnulls.md create mode 100644 docs/type-system/non-null.md create mode 100644 docs/type-system/object-types.md create mode 100644 docs/type-system/scalar-types.md create mode 100644 docs/type-system/schema.md diff --git a/docs/best-practices.md b/docs/best-practices.md new file mode 100644 index 0000000..90e7253 --- /dev/null +++ b/docs/best-practices.md @@ -0,0 +1,19 @@ +# Config Validation +Defining types using arrays may be error-prone, but **graphql-php** provides config validation +tool to report when config has unexpected structure. + +This validation tool is **disabled by default** because it is time-consuming operation which only +makes sense during development. + +To enable validation - call: `GraphQL\Type\Definition\Config::enableValidation();` in your bootstrap +but make sure to restrict it to debug/development mode only. + +# Type Registry +**graphql-php** expects that each type in Schema is presented with single instance. Therefore +if you define your types as separate PHP classes you need to ensure that each type is referenced only once. + +Technically you can create several instances of your type (for example for tests), but `GraphQL\Schema` +will throw on attempt to add different instances with the same name. + +There are several ways to achieve this depending on your preferences. We provide reference +implementation below that introduces TypeRegistry class: \ No newline at end of file diff --git a/docs/complementary-tools.md b/docs/complementary-tools.md new file mode 100644 index 0000000..0a221e2 --- /dev/null +++ b/docs/complementary-tools.md @@ -0,0 +1,3 @@ +# Relay + +# GraphiQL diff --git a/docs/overview.md b/docs/concepts.md similarity index 60% rename from docs/overview.md rename to docs/concepts.md index 284b740..97da24b 100644 --- a/docs/overview.md +++ b/docs/concepts.md @@ -1,14 +1,14 @@ -# Concepts +# Overview GraphQL is data-centric. On the very top level it is built around three major concepts: **Schema**, **Query** and **Mutation**. - -You are expected to expresses your application as **Schema** (aka Type System) and expose it -with single HTTP endpoint. Application clients (e.g. web or mobile clients) send **Queries** -to this endpoint to request structured data and **Mutations** to perform changes. - + +You are expected to express your application as **Schema** (aka Type System) and expose it +with single [HTTP endpoint](http-endpoint/). Application clients (e.g. web or mobile clients) send **Queries** +to this endpoint to request structured data and **Mutations** to perform changes (usually with HTTP POST method). + ## Queries Queries are expressed in simple language that resembles JSON: - + ```graphql { hero { @@ -19,7 +19,7 @@ Queries are expressed in simple language that resembles JSON: } } ``` - + It was designed to mirror the structure of expected response: ```json { @@ -34,11 +34,13 @@ It was designed to mirror the structure of expected response: } ``` **graphql-php** runtime parses Queries, makes sure that they are valid for given Type System -and executes using data resolving tools provided by you as a part of integration. +and executes using [data fetching tools](type-system/object-types/#data-fetching) provided by you +as a part of integration. Queries are supposed to be idempotent. ## Mutations Mutations use advanced features of the very same query language (like arguments and variables) and have only semantic difference from Queries: + ```graphql mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { @@ -78,17 +80,59 @@ returned after mutation. In our example mutation will return: ``` # Type System +Conceptually GraphQL type is a collection of fields. Each field in turn +has it's own type which allows to build complex hierarchies. + +Quick example on pseudo-language: +``` +type BlogPost { + title: String! + author: User + body: String +} + +type User { + id: Id! + firstName: String + lastName: String +} +``` + Type system is a heart of GraphQL integration. That's where **graphql-php** comes into play. It provides following tools and primitives to describe your App as hierarchy of types: - * Primitives to work with **objects** and **interfaces** + * Primitives for defining **objects** and **interfaces** * Primitives for defining **enumerations** and **unions** * Primitives for defining custom **scalar types** * Built-in scalar types: `ID`, `String`, `Int`, `Float`, `Boolean` * Built-in type modifiers: `ListOf` and `NonNull` -# Further Reading -To get deeper understanding of GraphQL concepts - [read the docs on official website](http://graphql.org/learn/) +Same example expressed in **graphql-php**: +```php + 'User', + 'fields' => [ + 'id' => Type::nonNull(Type::id()), + 'firstName' => Type::string(), + 'lastName' => Type::string() + ] +]); + +$blogPostType = new ObjectType([ + 'name' => 'BlogPost', + 'fields' => [ + 'title' => Type::nonNull(Type::string()), + 'author' => $userType + ] +]); +``` + +# Further Reading +To get deeper understanding of GraphQL concepts - [read the docs on official GraphQL website](http://graphql.org/learn/) + +To get started with **graphql-php** - continue to next section ["Getting Started"](getting-started/) diff --git a/docs/data-fetching.md b/docs/data-fetching.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/getting-started.md b/docs/getting-started.md index fa7a5d6..a563247 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,3 +1,7 @@ +# Prerequisites +This documentation assumes your familiarity with GraphQL concepts. If it is not the case - +first learn about GraphQL on [official website](http://graphql.org/learn/). + # Installation Using [composer](https://getcomposer.org/doc/00-intro.md): @@ -5,7 +9,7 @@ add `composer.json` file to your project root folder with following contents: ``` { "require": { - "webonyx/graphql-php": "^0.8" + "webonyx/graphql-php": "^0.7" } } ``` @@ -63,77 +67,13 @@ $queryType = new ObjectType([ ]); ``` -Same could be written as separate class: - -```php - [ - 'echo' => [ - 'type' => Type::string(), - 'args' => [ - 'message' => Type::nonNull(Type::string()), - ], - 'resolve' => function ($root, $args) { - return $root['prefix'] . $args['message']; - } - ], - ], - ]; - parent::__construct($config); - } -} -``` - -Or for those who prefer composition over inheritance: -```php -definition ?: ($this->definition = new \GraphQL\Type\Definition\ObjectType([ - 'name' => 'Query', - 'fields' => [ - 'echo' => [ - 'type' => Type::string(), - 'args' => [ - 'message' => Type::nonNull(Type::string()), - ], - 'resolve' => function ($root, $args) { - return $root['prefix'] . $args['message']; - } - ], - ], - ])); - } -} -``` +(Note: type definition can be expressed in [different styles](type-system/#type-definition-styles), +including **inheritance**, **composition** and **inline**. This example uses **inline** style for simplicity) The interesting piece here is `resolve` option of field definition. It is responsible for retuning -value for our field. **Scalar** values will be directly included in response while **complex object** -values will be passed down to nested field resolvers (not in this example though). - -Field resolvers is the main mechanism of **graphql-php** to bind type system with your -underlying data source. +value of our field. Values of **scalar** fields will be directly included in response while values of +**complex** fields (objects, interfaces, unions) will be passed down to nested field resolvers +(not in this example though). Now when our type is ready, let's create GraphQL endpoint for it `graphql.php`: @@ -143,7 +83,7 @@ use GraphQL\GraphQL; use GraphQL\Schema; $schema = new Schema([ - 'query' => $queryType, // or new MyApp\Type\QueryType() + 'query' => $queryType ]); $rawInput = file_get_contents('php://input'); @@ -168,11 +108,11 @@ php -S localhost:8000 graphql.php curl http://localhost:8000 -d "query { echo(message: \"Hello World\") }" ``` -Or grab the full [source code](https://github.com/webonyx/graphql-php/blob/master/examples/00-hello-world). -Obviously hello world only scratches the surface of what is possible. +Check out the full [source code](https://github.com/webonyx/graphql-php/blob/master/examples/00-hello-world) of this example. + +Obviously hello world only scratches the surface of what is possible. So check out next example, which is closer to real-world apps. - -Or just keep reading about [type system](types/) definitions. +Or keep reading about [schema definition](type-system/). # Blog example It is often easier to start with full-featured example and then get back to documentation diff --git a/docs/index.md b/docs/index.md index 1faaef5..af7291f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,9 +13,9 @@ All of them equally apply to this PHP implementation. # About graphql-php -**graphql-php** is an implementation of GraphQL specification in PHP (5.4+, 7.0+). -It is based on [JavaScript implementation](https://github.com/graphql/graphql-js) -published by Facebook as a reference for others. +**graphql-php** is a feature-complete implementation of GraphQL specification in PHP (5.4+, 7.0+). +It was originally inspired by [reference JavaScript implementation](https://github.com/graphql/graphql-js) +published by Facebook. This library is a thin wrapper around your existing data layer and business logic. It doesn't dictate how these layers are implemented or which storage engines @@ -24,54 +24,13 @@ are used. Instead it provides tools for creating rich API for your existing app. These tools include: - Primitives to express your app as a Type System - - Tools for validation and introspection of this Type System + - Tools for validation and introspection of this Type System (for compatibility with tools like [GraphiQL](complementary-tools/#graphiql)) - Tools for parsing, validating and executing GraphQL queries against this Type System - - Rich error reporting + - Rich error reporting, including query validation and execution errors + - Optional tools for parsing GraphQL Schema Definition language -## Usage Example -```php -use GraphQL\GraphQL; -use GraphQL\Schema; - -$query = ' -{ - hero { - id - name - friends { - name - } - } -} -'; - -$schema = new Schema([ - // ... - // Type System definition for your app goes here - // ... -]); - -$result = GraphQL::execute($schema, $query); -``` - -Result returned: -```php -[ - 'hero' => [ - 'id' => '2001', - 'name' => 'R2-D2', - 'friends' => [ - ['name' => 'Luke Skywalker'], - ['name' => 'Han Solo'], - ['name' => 'Leia Organa'], - ] - ] -] -``` - -Also check out full [Type System](https://github.com/webonyx/graphql-php/blob/master/tests/StarWarsSchema.php) -and [data source](https://github.com/webonyx/graphql-php/blob/master/tests/StarWarsData.php) -of this example. +Also several [complementary tools](complementary-tools/) are available which provide integrations with +existing PHP frameworks, add support for Relay, etc. ## Current Status Current version supports all features described by GraphQL specification diff --git a/docs/type-system.md b/docs/type-system.md deleted file mode 100644 index 7fb92d6..0000000 --- a/docs/type-system.md +++ /dev/null @@ -1 +0,0 @@ -TODOC diff --git a/docs/type-system/abstract-types.md b/docs/type-system/abstract-types.md new file mode 100644 index 0000000..1c629d7 --- /dev/null +++ b/docs/type-system/abstract-types.md @@ -0,0 +1,36 @@ +# Interfaces +In GraphQL an Interface is an abstract type that includes a certain set of fields that a +type must include to implement the interface. + +In **graphql-php** interface type is an instance of `GraphQL\Type\Definition\InterfaceType` +(or one of it subclasses) which accepts configuration array in constructor: + +```php +use GraphQL\Type\Definition\InterfaceType; +use GraphQL\Type\Definition\Type; + +$characterInterface = new InterfaceType([ + 'name' => 'Character', + 'description' => 'A character in the Star Wars Trilogy', + 'fields' => [ + 'id' => [ + 'type' => Type::nonNull(Type::string()), + 'description' => 'The id of the character.', + ], + 'name' => [ + 'type' => Type::string(), + 'description' => 'The name of the character.' + ] + ], + 'resolveType' => function ($obj) { + if ($obj->type === 'human') { + return MyTypes::human(); + } else { + return MyTypes::droid(); + } + return null; + } +]); +``` + +# Abstract Type Resolution diff --git a/docs/type-system/enum-types.md b/docs/type-system/enum-types.md new file mode 100644 index 0000000..22afa6b --- /dev/null +++ b/docs/type-system/enum-types.md @@ -0,0 +1,180 @@ +# Enum Type Definition +Enumeration types are a special kind of scalar that is restricted to a particular set +of allowed values. + +In **graphql-php** enum type is an instance of `GraphQL\Type\Definition\EnumType` +(or one of it subclasses) which accepts configuration array in constructor: + +```php +use GraphQL\Type\Definition\EnumType; + +$episodeEnum = new EnumType([ + 'name' => 'Episode', + 'description' => 'One of the films in the Star Wars Trilogy', + 'values' => [ + 'NEWHOPE' => [ + 'value' => 4, + 'description' => 'Released in 1977.' + ], + 'EMPIRE' => [ + 'value' => 5, + 'description' => 'Released in 1980.' + ], + 'JEDI' => [ + 'value' => 6, + 'description' => 'Released in 1983.' + ], + ] +]); +``` + +This example uses **inline** style for Enum Type definition, but there are also +[other styles](/type-system/#type-definition-styles) (using inheritance or composition). + +# Configuration options +Enum Type constructor accepts array with following options: + +Option | Type | Notes +------ | ---- | ----- +name | `string` | **Required.** Name of the type. When not set - inferred from array key (read about [shorthand field definition](#) below) +description | `string` | Plain-text description of the type for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) +values | `array` | List of enumerated items, see below for expected structure of each entry + +Each entry of **values** array in turn accepts following options: + +Option | Type | Notes +------ | ---- | ----- +name | `string` | **Required.** Name of the item. When not set - inferred from array key (read about [shorthand field definition](#) below) +value | `mixed` | Internal representation of enum item in your application (could be any value, including complex objects or callbacks) +description | `string` | Plain-text description of enum value for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) +deprecationReason | `string` | Text describing why this enum value is deprecated. When not empty - item will not be returned by introspection queries (unless forced) + + +# Shorthand definitions +If internal representation of enumerated item is the same as item name, then you can use +following shorthand for definition: + +```php +$episodeEnum = new EnumType([ + 'name' => 'Episode', + 'description' => 'One of the films in the Star Wars Trilogy', + 'values' => ['NEWHOPE', 'EMPIRE', 'JEDI'] +]); +``` + +which is equivalent of: +```php +$episodeEnum = new EnumType([ + 'name' => 'Episode', + 'description' => 'One of the films in the Star Wars Trilogy', + 'values' => [ + 'NEWHOPE' => 'NEWHOPE', + 'EMPIRE' => 'EMPIRE', + 'JEDI' => 'JEDI' + ] +]); +``` + +which is in turn equivalent of: +```php +$episodeEnum = new EnumType([ + 'name' => 'Episode', + 'description' => 'One of the films in the Star Wars Trilogy', + 'values' => [ + 'NEWHOPE' => ['value' => 'NEWHOPE'], + 'EMPIRE' => ['value' => 'EMPIRE'], + 'JEDI' => ['value' => 'JEDI'] + ] +]); +``` + +which is in turn equivalent of full form: + +```php +$episodeEnum = new EnumType([ + 'name' => 'Episode', + 'description' => 'One of the films in the Star Wars Trilogy', + 'values' => [ + ['name' => 'NEWHOPE', 'value' => 'NEWHOPE'], + ['name' => 'EMPIRE', 'value' => 'EMPIRE'], + ['name' => 'JEDI', 'value' => 'JEDI'] + ] +]); +``` + +# Field Resolution +When object field is of Enum Type, field resolver is expected to return internal +representation of corresponding Enum item (**value** in config). **graphql-php** will +then serialize this **value** to **name** to include in response: + +```php +use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\ObjectType; + +$episodeEnum = new EnumType([ + 'name' => 'Episode', + 'description' => 'One of the films in the Star Wars Trilogy', + 'values' => [ + 'NEWHOPE' => [ + 'value' => 4, + 'description' => 'Released in 1977.' + ], + 'EMPIRE' => [ + 'value' => 5, + 'description' => 'Released in 1980.' + ], + 'JEDI' => [ + 'value' => 6, + 'description' => 'Released in 1983.' + ], + ] +]); + +$heroType = new ObjectType([ + 'name' => 'Hero', + 'fields' => [ + 'appearsIn' => [ + 'type' => $episodeEnum, + 'resolve' => function() { + return 5; // Actual entry in response will be 'appearsIn' => 'EMPIRE' + } + ] + ] +]) +``` + +Reverse is true when enum is used as input type (e.g. as field argument). +GraphQL will treat enum input as **name** and convert it into **value** before passing to your app. + +For example, given object type definition: +```php +$heroType = new ObjectType([ + 'name' => 'Hero', + 'fields' => [ + 'appearsIn' => [ + 'type' => Type::boolean(), + 'args' => [ + 'episode' => Type::nonNull($enumType) + ] + 'resolve' => function($_value, $args) { + return $args['episode'] === 5 ? true : false; + } + ] + ] +]) +``` + +Then following query: +``` +fragment on Hero { + appearsInNewHope: appearsIn(NEWHOPE) + appearsInEmpire: appearsIn(EMPIRE) +} +``` +will return: +```php +[ + 'appearsInNewHope' => false, + 'appearsInEmpire' => true +] +``` diff --git a/docs/type-system/index.md b/docs/type-system/index.md new file mode 100644 index 0000000..f462545 --- /dev/null +++ b/docs/type-system/index.md @@ -0,0 +1,158 @@ +# Type System +To start using GraphQL you are expected to implement a Type system. + +In **graphql-php** `type` is an instance of internal class from +`GraphQL\Type\Definition` namespace: `ScalarType`, `ObjectType`, `InterfaceType`, +`UnionType`, `InputObjectType` (or one of it's subclasses). + +But most of the types in your schema will be [object types](object-types/). + +# Type Definition Styles +Several styles of type definitions are supported depending on your preferences. + +Inline definitions: +```php + 'MyType', + 'fields' => [ + 'id' => Type::id() + ] +]); +``` + +Class per type, using inheritance: +```php + [ + 'id' => Type::id() + ] + ]; + parent::__construct($config); + } +} +``` + +Class per type, using composition: +```php +definition ?: ($this->definition = new ObjectType([ + 'name' => 'MyType', + 'fields' => [ + 'id' => Type::id() + ] + ])); + } +} +``` + +You can also mix-and-match styles for convenience. For example: +```php + [ + 'body' => new ObjectType([ + 'name' => 'BlogPostBody', + 'fields' => [ + 'html' => Type::string(), + 'text' => Type::string(), + ] + ]) + ] + ]; + parent::__construct($config); + } +} +``` + +# Type Registry +Every type must be presented in Schema with single instance (**graphql-php** +throws when it discovers several instances with the same `name` in schema). + +Therefore if you define your type as separate PHP class you must ensure that only one +instance of that class is added to schema. + +Typical way to do this is to create registry of your types: + +```php +myAType ?: ($this->myAType = new MyAType($this)); + } + + public function myBType() + { + return $this->myBType ?: ($this->myBType = new MyBType($this)); + } +} +``` +And use this registry in type definition: + +```php + [ + 'b' => $types->myBType() + ] + ]); + } +} +``` +Obviously you can automate this registry as you wish to reduce boilerplate or even +introduce Dependency Injection Container if your types have other dependencies. + +Alternatively all methods of registry could be static if you prefer - then there is no need +to pass it in constructor - instead just use use `MyTypes::myAType()` in your type definitions. diff --git a/docs/type-system/lists-and-nonnulls.md b/docs/type-system/lists-and-nonnulls.md new file mode 100644 index 0000000..f061bb2 --- /dev/null +++ b/docs/type-system/lists-and-nonnulls.md @@ -0,0 +1,61 @@ +# Lists +**graphql-php** provides built-in support for lists. In order to create list type - wrap +existing type with `GraphQL\Type\Definition\Type::listOf()` modifier: +```php + 'User', + 'fields' => [ + 'emails' => [ + 'type' => Type::listOf(Type::string()), + 'resolve' => function() { + return ['jon@example.com', 'jonny@example.com']; + } + ] + ] +]); +``` + +Resolvers for such fields are expected to return `array` or instance of `Traversable` interface +(`null` is allowed by default too). + +If returned value is not of one of these types - **graphql-php** will add an error to result +and set field value to `null` (only if field is nullable, see below for non-null fields). + +# Non-Null types +By default in GraphQL every field can have `null` value. To indicate that some field always +returns `non-null` value - use `GraphQL\Type\Definition\Type::nonNull()` modifier: + +```php +use GraphQL\Type\Definition\Type; +use GraphQL\Type\Definition\ObjectType; + +$humanType = new ObjectType([ + 'name' => 'User', + 'fields' => [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + 'resolve' => function() { + return uniqid(); + } + ], + 'emails' => [ + 'type' => Type::nonNull(Type::listOf(Type::string())), + 'resolve' => function() { + return ['jon@example.com', 'jonny@example.com']; + } + ] + ] +]); +``` + +If resolver of non-null field returns `null`, **graphql-php** will add an error to +result and exclude whole object from output (error will bubble to first nullable parent +field which will be set to `null`). + +Read section on [Data Fetching](#) for details. diff --git a/docs/type-system/non-null.md b/docs/type-system/non-null.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/type-system/object-types.md b/docs/type-system/object-types.md new file mode 100644 index 0000000..dc3b48f --- /dev/null +++ b/docs/type-system/object-types.md @@ -0,0 +1,188 @@ +# Object Type Definition +Object Type is the most frequently used primitive in typical GraphQL application. + +Conceptually Object Type is a collection of Fields. Each field in turn +has it's own type which allows to build complex hierarchies. + +In **graphql-php** object type is an instance of `GraphQL\Type\Definition\ObjectType` +(or one of it subclasses) which accepts configuration array in constructor: + +```php + 'User', + 'description' => 'Our blog visitor', + 'fields' => [ + 'firstName' => [ + 'type' => Type::string(), + 'description' => 'User first name' + ], + 'email' => Type::string() + ] +]); + +$blogStory = new ObjectType([ + 'name' => 'Story', + 'fields' => [ + 'body' => Type::string(), + 'author' => [ + 'type' => $userType, + 'description' => 'Story author', + 'resolve' => function(Story $blogStory) { + return DataSource::findUser($blogStory->authorId); + } + ], + 'likes' => [ + 'type' => Type::listOf($userType), + 'description' => 'List of users who liked the story', + 'args' => [ + 'limit' => [ + 'type' => Type::int(), + 'description' => 'Limit the number of recent likes returned', + 'defaultValue' => 10 + ] + ], + 'resolve' => function(Story $blogStory, $args) { + return DataSource::findLikes($blogStory->id, $args['limit']); + } + ] + ] +]); +``` +This example uses **inline** style for Object Type definitions, but there are also +[other styles](/type-system/#type-definition-styles) (using inheritance or composition). + + +# Configuration options +Object type constructor expects configuration array. Below is a full list of available options: + +Option | Type | Notes +------------ | -------- | ----- +name | `string` | **Required.** Unique name of this object type within Schema +fields | `array` or `callback` returning `array` | **Required**. Array describing object fields. See [Fields](#field-definitions) section below for expected structure of each array entry. See also section on [Circular types](#) for explanation of when to use callback for this option. +description | `string` | Plain-text description of this type for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) +interfaces | `array` or `callback` returning `array` | List of interfaces implemented by this type. See [Interface Types](/type-system/interface-types) for details. See also section on [Circular types](#) for explanation of when to use callback for this option. +isTypeOf | `callback` returning `boolean` | **function($value, $context, GraphQL\Type\Definition\ResolveInfo $info)** Expected to return `true` if `$value` qualifies for this type (see section about [Abstract Type Resolution](#) for explanation). +resolveField | `callback` returning `mixed` | **function($value, $args, $context, GraphQL\Type\Definition\ResolveInfo $info)** Given the `$value` of this type it is expected to return value for field defined in `$info->fieldName`. Good place to define type-specific strategy for field resolution. See section on [Data Fetching](#) for details. + +# Field configuration options +Below is a full list of available field configuration options: + +Option | Type | Notes +------ | ---- | ----- +name | `string` | **Required.** Name of the field. When not set - inferred from array key (read about [shorthand field definition](#) below) +type | `Type` | **Required.** Instance of internal or custom type. Note: type must be represented by single instance within schema (see also [Type Registry](#)) +args | `array` | Array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue** +resolve | `callback` | **function($value, $args, $context, GraphQL\Type\Definition\ResolveInfo $info)** Given the `$value` of this type it is expected to return value for current field. See section on [Data Fetching](#) for details +description | `string` | Plain-text description of this field description for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) +deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced) + + +# Shorthand field definitions +Fields can be also defined in **shorthand** notation (without `description`, `args` and `defaultValue`): +```php +'fields' => [ + 'id' => Type::id(), + 'fieldName' => $fieldType +] +``` +which is equivalent of: +```php +'fields' => [ + 'id' => ['type' => Type::id()], + 'fieldName' => ['type' => $fieldName] +] +``` +which is in turn equivalent of full form: +```php +'fields' => [ + ['name' => 'id', 'type' => Type::id()], + ['name' => 'fieldName', 'type' => $fieldName] +] +``` +Same shorthand notation applies to field arguments as well. + +# Recurring and circular types +Almost all real-world applications contain recurring or circular types. +Think user friends or nested comments for example. + +**graphql-php** allows such types, but you have to use `callback` in +option **fields** (and/or **interfaces**). + +For example: +```php +$userType = null; + +$userType = new ObjectType([ + 'name' => 'User', + 'fields' => function() use (&$userType) { + return [ + 'email' => [ + 'type' => Type::string() + ], + 'friends' => [ + 'type' => Type::listOf($userType) + ] + ]; + } +]); +``` + +Same example for [inheritance style of type definitions](#) using [TypeRegistry](#): +```php + function() { + return [ + 'email' => MyTypes::string(), + 'friends' => MyTypes::listOf(MyTypes::user()) + ]; + } + ]; + parent::__construct($config); + } +} + +class MyTypes +{ + private static $user; + + public static function user() + { + return self::$user ?: (self::$user = new UserType()); + } + + public static function string() + { + return Type::string(); + } + + public static function listOf($type) + { + return Type::listOf($type); + } +} +``` + +# Field Resolution +Field resolution is the primary mechanism in **graphql-php** for returning actual data for your fields. +It is implemented using `resolveField` callback in type definition or `resolve` +callback in field definition (which has precedence). + +Read section on [Data Fetching]() for complete description of this process. \ No newline at end of file diff --git a/docs/type-system/scalar-types.md b/docs/type-system/scalar-types.md new file mode 100644 index 0000000..aa22a9f --- /dev/null +++ b/docs/type-system/scalar-types.md @@ -0,0 +1,148 @@ +# Built-in Scalar Types +GraphQL specification describes several built-in scalar types. In **graphql-php** they are +exposed as static methods of `GraphQL\Type\Definition\Type` class: + +```php +use GraphQL\Type\Definition\Type; + +// Built-in Scalar types: +Type::string(); // String type +Type::int(); // Int type +Type::float(); // Float type +Type::boolean(); // Boolean type +Type::id(); // ID type +``` +Those methods return instances of `GraphQL\Type\Definition\ScalarType` (actually one of it subclasses). +Use them directly in type definitions, or wrap in your [TypeRegistry](/type-system/#type-registry) +(if you use one). + +# Writing Custom Scalar Types +In addition to built-in scalars, you can define your own scalar types with additional validation. +Typical examples of such types are: `Email`, `Date`, `Url`, etc. + +In order to implement your own type you must understand how scalars are presented in GraphQL. +GraphQL deals with scalars in following cases: + +1. When converting **internal representation** of value returned by your app (e.g. stored in database +or hardcoded in source code) to **serialized** representation included in response. + +2. When converting **input value** passed by client in variables along with GraphQL query to +**internal representation** of your app. + +3. When converting **input literal value** hardcoded in GraphQL query (e.g. field argument value) to +**internal representation** of your app. + +Those cases are covered by methods `serialize`, `parseValue` and `parseLiteral` of abstract `ScalarType` +class respectively. + +Here is an example of simple `Email` type (using inheritance): + +```php +parseValue($value); + } + + /** + * Parses an externally provided value (query variable) to use as an input + * + * @param mixed $value + * @return mixed + */ + public function parseValue($value) + { + if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { + throw new \UnexpectedValueException("Cannot represent value as email: " . Utils::printSafe($value)); + } + return $value; + } + + /** + * Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input. + * + * E.g. + * { + * user(email: "user@example.com") + * } + * + * @param \GraphQL\Language\AST\Node $valueAST + * @return string + * @throws Error + */ + public function parseLiteral($valueAST) + { + // Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL + // error location in query: + if (!$valueAST instanceof StringValue) { + throw new Error('Query error: Can only parse strings got: ' . $valueAST->kind, [$valueAST]); + } + if (!filter_var($valueAST->value, FILTER_VALIDATE_EMAIL)) { + throw new Error("Not a valid email", [$valueAST]); + } + return $valueAST->value; + } +} +``` + +Same example, using composition over inheritance: +```php +definition ?: ($this->definition = new CustomScalarType([ + 'name' => 'Email', + 'serialize' => function($value) {/* See function body above */}, + 'parseValue' => function($value) {/* See function body above */}, + 'parseLiteral' => function($valueAST) {/* See function body above */}, + ])); + } +} +``` + +Or with inline style: + +```php + 'Email', + 'serialize' => function($value) {/* See function body above */}, + 'parseValue' => function($value) {/* See function body above */}, + 'parseLiteral' => function($valueAST) {/* See function body above */}, +]); +``` diff --git a/docs/type-system/schema.md b/docs/type-system/schema.md new file mode 100644 index 0000000..e69de29 diff --git a/mkdocs.yml b/mkdocs.yml index f3c9aa5..5ab30eb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,7 +1,16 @@ site_name: graphql-php pages: - About: index.md -- Overview: overview.md - Getting Started: getting-started.md -- Type System: type-system.md +- Schema Definition: + - Introduction: type-system/index.md + - Object Types: type-system/object-types.md + - Scalar Types: type-system/scalar-types.md + - Enumeration Types: type-system/enum-types.md + - Lists and Non-Null: type-system/lists-and-nonnulls.md + - Interfaces and Unions: type-system/abstract-types.md + - Defining Schema: type-system/schema.md +- Data Fetching: data-fetching.md +- Best Practices: best-practices.md +- Complementary Tools: complementary-tools.md theme: readthedocs