Ability to override internal types (closes #401)

This commit is contained in:
Vladimir Razuvaev 2018-11-27 15:39:20 +07:00
parent bdbb30c604
commit e1b4d438db
5 changed files with 208 additions and 16 deletions

View File

@ -1,9 +1,13 @@
# Changelog # Changelog
## dev-master ## dev-master
- Spec compliance: error extensions are displayed under `extensions` key This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details.
New features and notable changes:
- Spec compliance: error category, debug information and extensions are displayed under `extensions` key
- `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`) - `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`)
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`) - `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
- `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`) - `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`)
- Added ability to override standard types via `GraphQL::overrideStandardTypes(array $types)`
#### v0.12.5 #### v0.12.5
- Execution performance optimization for lists - Execution performance optimization for lists

View File

@ -276,7 +276,20 @@ class GraphQL
*/ */
public static function getStandardTypes() : array public static function getStandardTypes() : array
{ {
return array_values(Type::getInternalTypes()); return array_values(Type::getStandardTypes());
}
/**
* Replaces standard types with types from this list (matching by name)
* Standard types not listed here remain untouched.
*
* @param Type[] $types
*
* @api
*/
public static function overrideStandardTypes(array $types)
{
Type::overrideStandardTypes($types);
} }
/** /**
@ -291,6 +304,11 @@ class GraphQL
return array_values(DocumentValidator::defaultRules()); return array_values(DocumentValidator::defaultRules());
} }
/**
* Set default resolver implementation
*
* @api
*/
public static function setDefaultFieldResolver(callable $fn) : void public static function setDefaultFieldResolver(callable $fn) : void
{ {
Executor::setDefaultFieldResolver($fn); Executor::setDefaultFieldResolver($fn);

View File

@ -15,8 +15,11 @@ use ReflectionClass;
use Throwable; use Throwable;
use function array_keys; use function array_keys;
use function array_merge; use function array_merge;
use function implode;
use function in_array; use function in_array;
use function preg_replace; use function preg_replace;
use function trigger_error;
use const E_USER_DEPRECATED;
/** /**
* Registry of standard GraphQL types * Registry of standard GraphQL types
@ -31,7 +34,7 @@ abstract class Type implements JsonSerializable
public const ID = 'ID'; public const ID = 'ID';
/** @var Type[] */ /** @var Type[] */
private static $internalTypes; private static $standardTypes;
/** @var Type[] */ /** @var Type[] */
private static $builtInTypes; private static $builtInTypes;
@ -58,7 +61,7 @@ abstract class Type implements JsonSerializable
*/ */
public static function id() public static function id()
{ {
return self::getInternalType(self::ID); return self::getStandardType(self::ID);
} }
/** /**
@ -66,10 +69,10 @@ abstract class Type implements JsonSerializable
* *
* @return (IDType|StringType|FloatType|IntType|BooleanType)[]|IDType|StringType|FloatType|IntType|BooleanType * @return (IDType|StringType|FloatType|IntType|BooleanType)[]|IDType|StringType|FloatType|IntType|BooleanType
*/ */
private static function getInternalType($name = null) private static function getStandardType($name = null)
{ {
if (self::$internalTypes === null) { if (self::$standardTypes === null) {
self::$internalTypes = [ self::$standardTypes = [
self::ID => new IDType(), self::ID => new IDType(),
self::STRING => new StringType(), self::STRING => new StringType(),
self::FLOAT => new FloatType(), self::FLOAT => new FloatType(),
@ -78,7 +81,7 @@ abstract class Type implements JsonSerializable
]; ];
} }
return $name ? self::$internalTypes[$name] : self::$internalTypes; return $name ? self::$standardTypes[$name] : self::$standardTypes;
} }
/** /**
@ -88,7 +91,7 @@ abstract class Type implements JsonSerializable
*/ */
public static function string() public static function string()
{ {
return self::getInternalType(self::STRING); return self::getStandardType(self::STRING);
} }
/** /**
@ -98,7 +101,7 @@ abstract class Type implements JsonSerializable
*/ */
public static function boolean() public static function boolean()
{ {
return self::getInternalType(self::BOOLEAN); return self::getStandardType(self::BOOLEAN);
} }
/** /**
@ -108,7 +111,7 @@ abstract class Type implements JsonSerializable
*/ */
public static function int() public static function int()
{ {
return self::getInternalType(self::INT); return self::getStandardType(self::INT);
} }
/** /**
@ -118,7 +121,7 @@ abstract class Type implements JsonSerializable
*/ */
public static function float() public static function float()
{ {
return self::getInternalType(self::FLOAT); return self::getStandardType(self::FLOAT);
} }
/** /**
@ -166,7 +169,7 @@ abstract class Type implements JsonSerializable
if (self::$builtInTypes === null) { if (self::$builtInTypes === null) {
self::$builtInTypes = array_merge( self::$builtInTypes = array_merge(
Introspection::getTypes(), Introspection::getTypes(),
self::getInternalTypes() self::getStandardTypes()
); );
} }
@ -178,9 +181,44 @@ abstract class Type implements JsonSerializable
* *
* @return Type[] * @return Type[]
*/ */
public static function getStandardTypes()
{
return self::getStandardType();
}
/**
* @deprecated Use method getStandardTypes() instead
*
* @return Type[]
*/
public static function getInternalTypes() public static function getInternalTypes()
{ {
return self::getInternalType(); trigger_error(__METHOD__ . ' is deprecated. Use Type::getStandardTypes() instead', E_USER_DEPRECATED);
return self::getStandardTypes();
}
/**
* @param Type[] $types
*/
public static function overrideStandardTypes(array $types)
{
$standardTypes = self::getStandardTypes();
foreach ($types as $type) {
Utils::invariant(
$type instanceof Type,
'Expecting instance of %s, got %s',
self::class,
Utils::printSafe($type)
);
Utils::invariant(
isset($type->name, $standardTypes[$type->name]),
'Expecting one of the following names for a standard type: %s, got %s',
implode(', ', array_keys($standardTypes)),
Utils::printSafe($type->name ?? null)
);
$standardTypes[$type->name] = $type;
}
self::$standardTypes = $standardTypes;
} }
/** /**

View File

@ -143,7 +143,7 @@ class Schema
$this->resolvedTypes[$type->name] = $type; $this->resolvedTypes[$type->name] = $type;
} }
} }
$this->resolvedTypes += Type::getInternalTypes() + Introspection::getTypes(); $this->resolvedTypes += Type::getStandardTypes() + Introspection::getTypes();
if ($this->config->typeLoader) { if ($this->config->typeLoader) {
return; return;
@ -472,7 +472,7 @@ class Schema
throw new InvariantViolation(implode("\n\n", $this->validationErrors)); throw new InvariantViolation(implode("\n\n", $this->validationErrors));
} }
$internalTypes = Type::getInternalTypes() + Introspection::getTypes(); $internalTypes = Type::getStandardTypes() + Introspection::getTypes();
foreach ($this->getTypeMap() as $name => $type) { foreach ($this->getTypeMap() as $name => $type) {
if (isset($internalTypes[$name])) { if (isset($internalTypes[$name])) {
continue; continue;

View File

@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type;
use GraphQL\Error\InvariantViolation;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Type;
use PHPUnit\Framework\TestCase;
use stdClass;
class StandardTypesTest extends TestCase
{
/** @var Type[] */
private static $originalStandardTypes;
public static function setUpBeforeClass()
{
self::$originalStandardTypes = Type::getStandardTypes();
}
public function tearDown()
{
parent::tearDown();
Type::overrideStandardTypes(self::$originalStandardTypes);
}
public function testAllowsOverridingStandardTypes()
{
$originalTypes = Type::getStandardTypes();
self::assertCount(5, $originalTypes);
self::assertSame(self::$originalStandardTypes, $originalTypes);
$newBooleanType = $this->createCustomScalarType(Type::BOOLEAN);
$newFloatType = $this->createCustomScalarType(Type::FLOAT);
$newIDType = $this->createCustomScalarType(Type::ID);
$newIntType = $this->createCustomScalarType(Type::INT);
$newStringType = $this->createCustomScalarType(Type::STRING);
Type::overrideStandardTypes([
$newStringType,
$newBooleanType,
$newIDType,
$newIntType,
$newFloatType,
]);
$types = Type::getStandardTypes();
self::assertCount(5, $types);
self::assertSame($newBooleanType, $types[Type::BOOLEAN]);
self::assertSame($newFloatType, $types[Type::FLOAT]);
self::assertSame($newIDType, $types[Type::ID]);
self::assertSame($newIntType, $types[Type::INT]);
self::assertSame($newStringType, $types[Type::STRING]);
self::assertSame($newBooleanType, Type::boolean());
self::assertSame($newFloatType, Type::float());
self::assertSame($newIDType, Type::id());
self::assertSame($newIntType, Type::int());
self::assertSame($newStringType, Type::string());
}
public function testPreservesOriginalStandardTypes()
{
$originalTypes = Type::getStandardTypes();
self::assertCount(5, $originalTypes);
self::assertSame(self::$originalStandardTypes, $originalTypes);
$newIDType = $this->createCustomScalarType(Type::ID);
$newStringType = $this->createCustomScalarType(Type::STRING);
Type::overrideStandardTypes([
$newStringType,
$newIDType,
]);
$types = Type::getStandardTypes();
self::assertCount(5, $types);
self::assertSame($originalTypes[Type::BOOLEAN], $types[Type::BOOLEAN]);
self::assertSame($originalTypes[Type::FLOAT], $types[Type::FLOAT]);
self::assertSame($originalTypes[Type::INT], $types[Type::INT]);
self::assertSame($originalTypes[Type::BOOLEAN], Type::boolean());
self::assertSame($originalTypes[Type::FLOAT], Type::float());
self::assertSame($originalTypes[Type::INT], Type::int());
self::assertSame($newIDType, $types[Type::ID]);
self::assertSame($newStringType, $types[Type::STRING]);
self::assertSame($newIDType, Type::id());
self::assertSame($newStringType, Type::string());
}
public function getInvalidStandardTypes()
{
return [
[null, 'Expecting instance of GraphQL\Type\Definition\Type, got null'],
[5, 'Expecting instance of GraphQL\Type\Definition\Type, got 5'],
['', 'Expecting instance of GraphQL\Type\Definition\Type, got (empty string)'],
[new stdClass(), 'Expecting instance of GraphQL\Type\Definition\Type, got instance of stdClass'],
[[], 'Expecting instance of GraphQL\Type\Definition\Type, got []'],
[$this->createCustomScalarType('NonStandardName'), 'Expecting one of the following names for a standard type: ID, String, Float, Int, Boolean, got NonStandardName'],
];
}
/**
* @dataProvider getInvalidStandardTypes
*/
public function testStandardTypesOverrideDoesSanityChecks($type, string $expectedMessage)
{
$this->expectException(InvariantViolation::class);
$this->expectExceptionMessage($expectedMessage);
Type::overrideStandardTypes([ $type ]);
}
private function createCustomScalarType($name)
{
return new CustomScalarType([
'name' => $name,
'serialize' => static function () {
},
'parseValue' => static function () {
},
'parseLiteral' => static function () {
},
]);
}
}