Added deprecated directive; changed custom directives handling in schema; various minor tweaks

This commit is contained in:
vladar 2016-10-19 01:34:46 +07:00
parent 7625d6abf1
commit 236021acf8
14 changed files with 238 additions and 17 deletions

View File

@ -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)

View File

@ -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)];
} }

View File

@ -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());
}
} }

View File

@ -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;
} }

View File

@ -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();
}
} }

View File

@ -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;
} }

View File

@ -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
])
]
]) ])
]; ];
} }

View File

@ -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()

View File

@ -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()

View File

@ -36,6 +36,11 @@ class ResolveInfo
*/ */
public $parentType; public $parentType;
/**
* @var array
*/
public $path;
/** /**
* @var Schema * @var Schema
*/ */

View File

@ -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;
} }

View File

@ -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()
* *

View File

@ -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

View File

@ -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;
} }