mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-21 20:36:05 +03:00
Work in progress on better docs
This commit is contained in:
parent
eb3f54b98e
commit
e69667c633
19
docs/best-practices.md
Normal file
19
docs/best-practices.md
Normal file
@ -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:
|
3
docs/complementary-tools.md
Normal file
3
docs/complementary-tools.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Relay
|
||||
|
||||
# GraphiQL
|
@ -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
|
||||
<?php
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
|
||||
To get started with your own app - continue to next section ["Getting Started"](getting-started/)
|
||||
$userType = new ObjectType([
|
||||
'name' => '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/)
|
0
docs/data-fetching.md
Normal file
0
docs/data-fetching.md
Normal file
@ -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
|
||||
<?php
|
||||
namespace MyApp\Type;
|
||||
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class QueryType extends ObjectType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
// Note: name is not required in this form, as it will be inferred
|
||||
// from className ("Type" suffix will be dropped)
|
||||
'fields' => [
|
||||
'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
|
||||
<?php
|
||||
namespace MyApp\Type;
|
||||
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\DefinitionContainer;
|
||||
|
||||
class QueryType implements DefinitionContainer
|
||||
{
|
||||
private $definition;
|
||||
|
||||
public function getDefinition()
|
||||
{
|
||||
return $this->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
|
||||
|
@ -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
|
||||
|
@ -1 +0,0 @@
|
||||
TODOC
|
36
docs/type-system/abstract-types.md
Normal file
36
docs/type-system/abstract-types.md
Normal file
@ -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
|
180
docs/type-system/enum-types.md
Normal file
180
docs/type-system/enum-types.md
Normal file
@ -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
|
||||
]
|
||||
```
|
158
docs/type-system/index.md
Normal file
158
docs/type-system/index.md
Normal file
@ -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
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
$myType = new ObjectType([
|
||||
'name' => 'MyType',
|
||||
'fields' => [
|
||||
'id' => Type::id()
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
Class per type, using inheritance:
|
||||
```php
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class MyType extends ObjectType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
// Note: 'name' is not needed in this form:
|
||||
// it will be inferred from class name by omitting namespace and dropping "Type" suffix
|
||||
'fields' => [
|
||||
'id' => Type::id()
|
||||
]
|
||||
];
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Class per type, using composition:
|
||||
```php
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\DefinitionContainer;
|
||||
|
||||
class MyType implements DefinitionContainer
|
||||
{
|
||||
private $definition;
|
||||
|
||||
public function getDefinition()
|
||||
{
|
||||
return $this->definition ?: ($this->definition = new ObjectType([
|
||||
'name' => 'MyType',
|
||||
'fields' => [
|
||||
'id' => Type::id()
|
||||
]
|
||||
]));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also mix-and-match styles for convenience. For example:
|
||||
```php
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class BlogPostType extends ObjectType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'fields' => [
|
||||
'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
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
class TypeRegistry
|
||||
{
|
||||
private $myAType;
|
||||
private $myBType;
|
||||
|
||||
public function myAType()
|
||||
{
|
||||
return $this->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
|
||||
<?php
|
||||
namespace MyApp;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
|
||||
class MyAType extends ObjectType
|
||||
{
|
||||
public function __construct(TypeRegistry $types)
|
||||
{
|
||||
parent::__construct([
|
||||
'fields' => [
|
||||
'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.
|
61
docs/type-system/lists-and-nonnulls.md
Normal file
61
docs/type-system/lists-and-nonnulls.md
Normal file
@ -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
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
|
||||
$userType = new ObjectType([
|
||||
'name' => '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.
|
0
docs/type-system/non-null.md
Normal file
0
docs/type-system/non-null.md
Normal file
188
docs/type-system/object-types.md
Normal file
188
docs/type-system/object-types.md
Normal file
@ -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
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Examples\Blog\Data\DataSource;
|
||||
use GraphQL\Examples\Blog\Data\Story;
|
||||
|
||||
$userType = new ObjectType([
|
||||
'name' => '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
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
|
||||
class UserType extends ObjectType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'fields' => 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.
|
148
docs/type-system/scalar-types.md
Normal file
148
docs/type-system/scalar-types.md
Normal file
@ -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
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\StringValue;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Utils;
|
||||
|
||||
class EmailType extends ScalarType
|
||||
{
|
||||
// Note: name can be omitted. In this case it will be inferred from class name
|
||||
// (suffix "Type" will be dropped)
|
||||
public $name = 'Email';
|
||||
|
||||
/**
|
||||
* Serializes an internal value to include in a response.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public function serialize($value)
|
||||
{
|
||||
// Assuming internal representation of email is always correct:
|
||||
return $value;
|
||||
|
||||
// If it might be incorrect and you want to make sure that only correct values are included in response -
|
||||
// use following line instead:
|
||||
// return $this->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
|
||||
<?php
|
||||
namespace MyApp;
|
||||
|
||||
use GraphQL\Type\DefinitionContainer;
|
||||
use GraphQL\Type\Definition\CustomScalarType;
|
||||
|
||||
class EmailType implements DefinitionContainer
|
||||
{
|
||||
private $definition;
|
||||
|
||||
public function getDefinition()
|
||||
{
|
||||
return $this->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
|
||||
<?php
|
||||
use GraphQL\Type\Definition\CustomScalarType;
|
||||
|
||||
$emailType = 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 */},
|
||||
]);
|
||||
```
|
0
docs/type-system/schema.md
Normal file
0
docs/type-system/schema.md
Normal file
13
mkdocs.yml
13
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
|
||||
|
Loading…
Reference in New Issue
Block a user