mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 14:26:08 +03:00
Added deprecated directive; changed custom directives handling in schema; various minor tweaks
This commit is contained in:
parent
7625d6abf1
commit
236021acf8
24
UPGRADE.md
24
UPGRADE.md
@ -2,7 +2,27 @@
|
|||||||
|
|
||||||
## Upgrade v0.7.x > v1.0.x
|
## Upgrade v0.7.x > v1.0.x
|
||||||
|
|
||||||
### 1. Protected property and method naming
|
### 1. Custom directives handling
|
||||||
|
When passing custom directives to schema, default directives (like `@skip` and `@include`)
|
||||||
|
are not added to schema automatically anymore. If you need them - add them explicitly with your other directives
|
||||||
|
|
||||||
|
Before the change:
|
||||||
|
```php
|
||||||
|
$schema = new Schema([
|
||||||
|
// ...
|
||||||
|
'directives' => [$myDirective]
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
After the change:
|
||||||
|
```php
|
||||||
|
$schema = new Schema([
|
||||||
|
// ...
|
||||||
|
'directives' => array_merge(GraphQL::getInternalDirectives(), [$myDirective])
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Protected property and method naming
|
||||||
In order to unify coding style, leading underscores were removed from all private and protected properties
|
In order to unify coding style, leading underscores were removed from all private and protected properties
|
||||||
and methods.
|
and methods.
|
||||||
|
|
||||||
@ -19,7 +39,7 @@ GraphQL\Schema::$queryType;
|
|||||||
So if you rely on any protected properties or methods of any GraphQL class, make sure to
|
So if you rely on any protected properties or methods of any GraphQL class, make sure to
|
||||||
delete leading underscores.
|
delete leading underscores.
|
||||||
|
|
||||||
### 2. Returning closure from field resolver
|
### 3. Returning closure from field resolver
|
||||||
Previously when you returned closure from any resolver, expected signature of this closure
|
Previously when you returned closure from any resolver, expected signature of this closure
|
||||||
was `function($sourceValue)`, new signature is `function($args, $context)`
|
was `function($sourceValue)`, new signature is `function($args, $context)`
|
||||||
(now mirrors reference graphql-js implementation)
|
(now mirrors reference graphql-js implementation)
|
||||||
|
@ -234,11 +234,10 @@ class Values
|
|||||||
|
|
||||||
if ($type instanceof ListOfType) {
|
if ($type instanceof ListOfType) {
|
||||||
$itemType = $type->getWrappedType();
|
$itemType = $type->getWrappedType();
|
||||||
// TODO: support iterable input
|
if (is_array($value) || $value instanceof \Traversable) {
|
||||||
if (is_array($value)) {
|
return Utils::map($value, function($item) use ($itemType) {
|
||||||
return array_map(function ($item) use ($itemType) {
|
|
||||||
return Values::coerceValue($itemType, $item);
|
return Values::coerceValue($itemType, $item);
|
||||||
}, $value);
|
});
|
||||||
} else {
|
} else {
|
||||||
return [self::coerceValue($itemType, $value)];
|
return [self::coerceValue($itemType, $value)];
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use GraphQL\Executor\Executor;
|
|||||||
use GraphQL\Language\AST\Document;
|
use GraphQL\Language\AST\Document;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
|
use GraphQL\Type\Definition\Directive;
|
||||||
use GraphQL\Validator\DocumentValidator;
|
use GraphQL\Validator\DocumentValidator;
|
||||||
use GraphQL\Validator\Rules\QueryComplexity;
|
use GraphQL\Validator\Rules\QueryComplexity;
|
||||||
|
|
||||||
@ -57,4 +58,12 @@ class GraphQL
|
|||||||
return new ExecutionResult(null, [$e]);
|
return new ExecutionResult(null, [$e]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getInternalDirectives()
|
||||||
|
{
|
||||||
|
return array_values(Directive::getInternalDirectives());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ class Document extends Node
|
|||||||
public $kind = Node::DOCUMENT;
|
public $kind = Node::DOCUMENT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<Definition>
|
* @var Definition[]
|
||||||
*/
|
*/
|
||||||
public $definitions;
|
public $definitions;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Language;
|
namespace GraphQL\Language;
|
||||||
|
|
||||||
class SourceLocation
|
class SourceLocation implements \JsonSerializable
|
||||||
{
|
{
|
||||||
public $line;
|
public $line;
|
||||||
public $column;
|
public $column;
|
||||||
@ -12,6 +12,9 @@ class SourceLocation
|
|||||||
$this->column = $col;
|
$this->column = $col;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public function toArray()
|
public function toArray()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -19,4 +22,24 @@ class SourceLocation
|
|||||||
'column' => $this->column
|
'column' => $this->column
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toSerializableArray()
|
||||||
|
{
|
||||||
|
return $this->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify data which should be serialized to JSON
|
||||||
|
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||||
|
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||||
|
* which is a value of any type other than a resource.
|
||||||
|
* @since 5.4.0
|
||||||
|
*/
|
||||||
|
function jsonSerialize()
|
||||||
|
{
|
||||||
|
return $this->toSerializableArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,30 @@ use GraphQL\Type\Definition\WrappingType;
|
|||||||
use GraphQL\Type\Introspection;
|
use GraphQL\Type\Introspection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Schema
|
* Schema Definition
|
||||||
|
*
|
||||||
|
* A Schema is created by supplying the root types of each type of operation:
|
||||||
|
* query, mutation (optional) and subscription (optional). A schema definition is
|
||||||
|
* then supplied to the validator and executor.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* $schema = new GraphQL\Schema([
|
||||||
|
* 'query' => $MyAppQueryRootType,
|
||||||
|
* 'mutation' => $MyAppMutationRootType,
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* Note: If an array of `directives` are provided to GraphQL\Schema, that will be
|
||||||
|
* the exact list of directives represented and allowed. If `directives` is not
|
||||||
|
* provided then a default set of the specified directives (e.g. @include and
|
||||||
|
* @skip) will be used. If you wish to provide *additional* directives to these
|
||||||
|
* specified directives, you must explicitly declare them. Example:
|
||||||
|
*
|
||||||
|
* $mySchema = new GraphQL\Schema([
|
||||||
|
* ...
|
||||||
|
* 'directives' => array_merge(GraphQL::getInternalDirectives(), [ $myCustomDirective ]),
|
||||||
|
* ])
|
||||||
|
*
|
||||||
* @package GraphQL
|
* @package GraphQL
|
||||||
*/
|
*/
|
||||||
class Schema
|
class Schema
|
||||||
@ -119,10 +142,7 @@ class Schema
|
|||||||
"Schema directives must be Directive[] if provided but got " . Utils::getVariableType($config['directives'])
|
"Schema directives must be Directive[] if provided but got " . Utils::getVariableType($config['directives'])
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->directives = array_merge($config['directives'], [
|
$this->directives = $config['directives'] ?: GraphQL::getInternalDirectives();
|
||||||
Directive::includeDirective(),
|
|
||||||
Directive::skipDirective()
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Build type map now to detect any errors within this schema.
|
// Build type map now to detect any errors within this schema.
|
||||||
$initialTypes = [
|
$initialTypes = [
|
||||||
@ -222,6 +242,15 @@ class Schema
|
|||||||
foreach ($this->getPossibleTypes($abstractType) as $type) {
|
foreach ($this->getPossibleTypes($abstractType) as $type) {
|
||||||
$tmp[$type->name] = true;
|
$tmp[$type->name] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Utils::invariant(
|
||||||
|
!empty($tmp),
|
||||||
|
'Could not find possible implementing types for $%s ' .
|
||||||
|
'in schema. Check that schema.types is defined and is an array of ' .
|
||||||
|
'all possible types in the schema.',
|
||||||
|
$abstractType->name
|
||||||
|
);
|
||||||
|
|
||||||
$this->possibleTypeMap[$abstractType->name] = $tmp;
|
$this->possibleTypeMap[$abstractType->name] = $tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ namespace GraphQL\Type\Definition;
|
|||||||
*/
|
*/
|
||||||
class Directive
|
class Directive
|
||||||
{
|
{
|
||||||
|
const DEFAULT_DEPRECATION_REASON = 'No longer supported';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -16,6 +18,7 @@ class Directive
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public static $directiveLocations = [
|
public static $directiveLocations = [
|
||||||
|
// Operations:
|
||||||
'QUERY' => 'QUERY',
|
'QUERY' => 'QUERY',
|
||||||
'MUTATION' => 'MUTATION',
|
'MUTATION' => 'MUTATION',
|
||||||
'SUBSCRIPTION' => 'SUBSCRIPTION',
|
'SUBSCRIPTION' => 'SUBSCRIPTION',
|
||||||
@ -23,6 +26,19 @@ class Directive
|
|||||||
'FRAGMENT_DEFINITION' => 'FRAGMENT_DEFINITION',
|
'FRAGMENT_DEFINITION' => 'FRAGMENT_DEFINITION',
|
||||||
'FRAGMENT_SPREAD' => 'FRAGMENT_SPREAD',
|
'FRAGMENT_SPREAD' => 'FRAGMENT_SPREAD',
|
||||||
'INLINE_FRAGMENT' => 'INLINE_FRAGMENT',
|
'INLINE_FRAGMENT' => 'INLINE_FRAGMENT',
|
||||||
|
|
||||||
|
// Schema Definitions
|
||||||
|
'SCHEMA' => 'SCHEMA',
|
||||||
|
'SCALAR' => 'SCALAR',
|
||||||
|
'OBJECT' => 'OBJECT',
|
||||||
|
'FIELD_DEFINITION' => 'FIELD_DEFINITION',
|
||||||
|
'ARGUMENT_DEFINITION' => 'ARGUMENT_DEFINITION',
|
||||||
|
'INTERFACE' => 'INTERFACE',
|
||||||
|
'UNION' => 'UNION',
|
||||||
|
'ENUM' => 'ENUM',
|
||||||
|
'ENUM_VALUE' => 'ENUM_VALUE',
|
||||||
|
'INPUT_OBJECT' => 'INPUT_OBJECT',
|
||||||
|
'INPUT_FIELD_DEFINITION' => 'INPUT_FIELD_DEFINITION'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,6 +59,15 @@ class Directive
|
|||||||
return $internal['skip'];
|
return $internal['skip'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Directive
|
||||||
|
*/
|
||||||
|
public static function deprecatedDirective()
|
||||||
|
{
|
||||||
|
$internal = self::getInternalDirectives();
|
||||||
|
return $internal['deprecated'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
@ -81,6 +106,25 @@ class Directive
|
|||||||
'description' => 'Skipped when true'
|
'description' => 'Skipped when true'
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
|
]),
|
||||||
|
'deprecated' => new self([
|
||||||
|
'name' => 'deprecated',
|
||||||
|
'description' => 'Marks an element of a GraphQL schema as no longer supported.',
|
||||||
|
'locations' => [
|
||||||
|
self::$directiveLocations['FIELD_DEFINITION'],
|
||||||
|
self::$directiveLocations['ENUM_VALUE']
|
||||||
|
],
|
||||||
|
'args' => [
|
||||||
|
new FieldArgument([
|
||||||
|
'name' => 'reason',
|
||||||
|
'type' => Type::string(),
|
||||||
|
'description' =>
|
||||||
|
'Explains why this element was deprecated, usually also including a ' .
|
||||||
|
'suggestion for how to access supported similar data. Formatted ' .
|
||||||
|
'in [Markdown](https://daringfireball.net/projects/markdown/).',
|
||||||
|
'defaultValue' => self::DEFAULT_DEPRECATION_REASON
|
||||||
|
])
|
||||||
|
]
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,6 @@ class FieldDefinition
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated as of 17.10.2016 in favor of setting 'fields' as closure per ObjectType vs setting on field level
|
|
||||||
* @return Type
|
* @return Type
|
||||||
*/
|
*/
|
||||||
public function getType()
|
public function getType()
|
||||||
|
@ -39,7 +39,6 @@ class InputObjectField
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated in favor of defining all object 'fields' as closure vs defining closure per field
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function getType()
|
public function getType()
|
||||||
|
@ -36,6 +36,11 @@ class ResolveInfo
|
|||||||
*/
|
*/
|
||||||
public $parentType;
|
public $parentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Schema
|
* @var Schema
|
||||||
*/
|
*/
|
||||||
|
@ -72,6 +72,11 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
|
|||||||
{
|
{
|
||||||
if ($this->types instanceof \Closure) {
|
if ($this->types instanceof \Closure) {
|
||||||
$this->types = call_user_func($this->types);
|
$this->types = call_user_func($this->types);
|
||||||
|
Utils::invariant(
|
||||||
|
is_array($this->types),
|
||||||
|
'Closure for option "types" of union "%s" is expected to return array of types',
|
||||||
|
$this->name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return $this->types;
|
return $this->types;
|
||||||
}
|
}
|
||||||
|
@ -221,6 +221,31 @@ class Utils
|
|||||||
return is_object($var) ? get_class($var) : gettype($var);
|
return is_object($var) ? get_class($var) : gettype($var);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $var
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function printSafe($var)
|
||||||
|
{
|
||||||
|
if ($var instanceof Type) {
|
||||||
|
// FIXME: Replace with schema printer call
|
||||||
|
if ($var instanceof WrappingType) {
|
||||||
|
$var = $var->getWrappedType(true);
|
||||||
|
}
|
||||||
|
return $var->name;
|
||||||
|
}
|
||||||
|
if (is_object($var)) {
|
||||||
|
return 'instance of ' . get_class($var);
|
||||||
|
}
|
||||||
|
if (is_scalar($var)) {
|
||||||
|
return (string) $var;
|
||||||
|
}
|
||||||
|
if (null === $var) {
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
return gettype($var);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UTF-8 compatible chr()
|
* UTF-8 compatible chr()
|
||||||
*
|
*
|
||||||
|
@ -225,6 +225,54 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($sub->name, 'articleSubscribe');
|
$this->assertEquals($sub->name, 'articleSubscribe');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it defines an enum type with deprecated value
|
||||||
|
*/
|
||||||
|
public function testDefinesEnumTypeWithDeprecatedValue()
|
||||||
|
{
|
||||||
|
$enumTypeWithDeprecatedValue = new EnumType([
|
||||||
|
'name' => 'EnumWithDeprecatedValue',
|
||||||
|
'values' => [
|
||||||
|
'foo' => ['deprecationReason' => 'Just because']
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$value = $enumTypeWithDeprecatedValue->getValues()[0];
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'name' => 'foo',
|
||||||
|
'description' => null,
|
||||||
|
'deprecationReason' => 'Just because',
|
||||||
|
'value' => 'foo'
|
||||||
|
], (array) $value);
|
||||||
|
|
||||||
|
$this->assertEquals(true, $value->isDeprecated());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it defines an object type with deprecated field
|
||||||
|
*/
|
||||||
|
public function testDefinesAnObjectTypeWithDeprecatedField()
|
||||||
|
{
|
||||||
|
$TypeWithDeprecatedField = new ObjectType([
|
||||||
|
'name' => 'foo',
|
||||||
|
'fields' => [
|
||||||
|
'bar' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'deprecationReason' => 'A terrible reason'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$field = $TypeWithDeprecatedField->getField('bar');
|
||||||
|
|
||||||
|
$this->assertEquals(Type::string(), $field->getType());
|
||||||
|
$this->assertEquals(true, $field->isDeprecated());
|
||||||
|
$this->assertEquals('A terrible reason', $field->deprecationReason);
|
||||||
|
$this->assertEquals('bar', $field->name);
|
||||||
|
$this->assertEquals([], $field->args);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @it includes nested input objects in the map
|
* @it includes nested input objects in the map
|
||||||
*/
|
*/
|
||||||
@ -424,6 +472,21 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
|||||||
Config::disableValidation();
|
Config::disableValidation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it allows a thunk for Union\'s types
|
||||||
|
*/
|
||||||
|
public function testAllowsThunkForUnionTypes()
|
||||||
|
{
|
||||||
|
$union = new UnionType([
|
||||||
|
'name' => 'ThunkUnion',
|
||||||
|
'types' => function() {return [$this->objectType]; }
|
||||||
|
]);
|
||||||
|
|
||||||
|
$types = $union->getTypes();
|
||||||
|
$this->assertEquals(1, count($types));
|
||||||
|
$this->assertSame($this->objectType, $types[0]);
|
||||||
|
}
|
||||||
|
|
||||||
public function testAllowsRecursiveDefinitions()
|
public function testAllowsRecursiveDefinitions()
|
||||||
{
|
{
|
||||||
// See https://github.com/webonyx/graphql-php/issues/16
|
// See https://github.com/webonyx/graphql-php/issues/16
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Tests\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
|
use GraphQL\GraphQL;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Schema;
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
@ -291,12 +292,12 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
$defaultSchema = new Schema([
|
$defaultSchema = new Schema([
|
||||||
'query' => $queryRoot,
|
'query' => $queryRoot,
|
||||||
'directives' => [
|
'directives' => array_merge(GraphQL::getInternalDirectives(), [
|
||||||
new Directive([
|
new Directive([
|
||||||
'name' => 'operationOnly',
|
'name' => 'operationOnly',
|
||||||
'locations' => [ 'QUERY' ],
|
'locations' => [ 'QUERY' ],
|
||||||
])
|
])
|
||||||
]
|
])
|
||||||
]);
|
]);
|
||||||
return $defaultSchema;
|
return $defaultSchema;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user