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
### 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
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
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
was `function($sourceValue)`, new signature is `function($args, $context)`
(now mirrors reference graphql-js implementation)

View File

@ -234,11 +234,10 @@ class Values
if ($type instanceof ListOfType) {
$itemType = $type->getWrappedType();
// TODO: support iterable input
if (is_array($value)) {
return array_map(function ($item) use ($itemType) {
if (is_array($value) || $value instanceof \Traversable) {
return Utils::map($value, function($item) use ($itemType) {
return Values::coerceValue($itemType, $item);
}, $value);
});
} else {
return [self::coerceValue($itemType, $value)];
}

View File

@ -6,6 +6,7 @@ use GraphQL\Executor\Executor;
use GraphQL\Language\AST\Document;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Type\Definition\Directive;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\QueryComplexity;
@ -57,4 +58,12 @@ class GraphQL
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;
/**
* @var array<Definition>
* @var Definition[]
*/
public $definitions;
}

View File

@ -1,7 +1,7 @@
<?php
namespace GraphQL\Language;
class SourceLocation
class SourceLocation implements \JsonSerializable
{
public $line;
public $column;
@ -12,6 +12,9 @@ class SourceLocation
$this->column = $col;
}
/**
* @return array
*/
public function toArray()
{
return [
@ -19,4 +22,24 @@ class SourceLocation
'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;
/**
* 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
*/
class Schema
@ -119,10 +142,7 @@ class Schema
"Schema directives must be Directive[] if provided but got " . Utils::getVariableType($config['directives'])
);
$this->directives = array_merge($config['directives'], [
Directive::includeDirective(),
Directive::skipDirective()
]);
$this->directives = $config['directives'] ?: GraphQL::getInternalDirectives();
// Build type map now to detect any errors within this schema.
$initialTypes = [
@ -222,6 +242,15 @@ class Schema
foreach ($this->getPossibleTypes($abstractType) as $type) {
$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;
}

View File

@ -7,6 +7,8 @@ namespace GraphQL\Type\Definition;
*/
class Directive
{
const DEFAULT_DEPRECATION_REASON = 'No longer supported';
/**
* @var array
*/
@ -16,6 +18,7 @@ class Directive
* @var array
*/
public static $directiveLocations = [
// Operations:
'QUERY' => 'QUERY',
'MUTATION' => 'MUTATION',
'SUBSCRIPTION' => 'SUBSCRIPTION',
@ -23,6 +26,19 @@ class Directive
'FRAGMENT_DEFINITION' => 'FRAGMENT_DEFINITION',
'FRAGMENT_SPREAD' => 'FRAGMENT_SPREAD',
'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 Directive
*/
public static function deprecatedDirective()
{
$internal = self::getInternalDirectives();
return $internal['deprecated'];
}
/**
* @return array
*/
@ -81,6 +106,25 @@ class Directive
'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
*/
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
*/
public function getType()

View File

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

View File

@ -72,6 +72,11 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
{
if ($this->types instanceof \Closure) {
$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;
}

View File

@ -221,6 +221,31 @@ class Utils
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()
*

View File

@ -225,6 +225,54 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
$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
*/
@ -424,6 +472,21 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
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()
{
// See https://github.com/webonyx/graphql-php/issues/16

View File

@ -1,6 +1,7 @@
<?php
namespace GraphQL\Tests\Validator;
use GraphQL\GraphQL;
use GraphQL\Language\Parser;
use GraphQL\Schema;
use GraphQL\Type\Definition\Directive;
@ -291,12 +292,12 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
$defaultSchema = new Schema([
'query' => $queryRoot,
'directives' => [
'directives' => array_merge(GraphQL::getInternalDirectives(), [
new Directive([
'name' => 'operationOnly',
'locations' => [ 'QUERY' ],
])
]
])
]);
return $defaultSchema;
}