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
## 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`)
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
- `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`)
- Added ability to override standard types via `GraphQL::overrideStandardTypes(array $types)`
#### v0.12.5
- Execution performance optimization for lists

View File

@ -276,7 +276,20 @@ class GraphQL
*/
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());
}
/**
* Set default resolver implementation
*
* @api
*/
public static function setDefaultFieldResolver(callable $fn) : void
{
Executor::setDefaultFieldResolver($fn);

View File

@ -15,8 +15,11 @@ use ReflectionClass;
use Throwable;
use function array_keys;
use function array_merge;
use function implode;
use function in_array;
use function preg_replace;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Registry of standard GraphQL types
@ -31,7 +34,7 @@ abstract class Type implements JsonSerializable
public const ID = 'ID';
/** @var Type[] */
private static $internalTypes;
private static $standardTypes;
/** @var Type[] */
private static $builtInTypes;
@ -58,7 +61,7 @@ abstract class Type implements JsonSerializable
*/
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
*/
private static function getInternalType($name = null)
private static function getStandardType($name = null)
{
if (self::$internalTypes === null) {
self::$internalTypes = [
if (self::$standardTypes === null) {
self::$standardTypes = [
self::ID => new IDType(),
self::STRING => new StringType(),
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()
{
return self::getInternalType(self::STRING);
return self::getStandardType(self::STRING);
}
/**
@ -98,7 +101,7 @@ abstract class Type implements JsonSerializable
*/
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()
{
return self::getInternalType(self::INT);
return self::getStandardType(self::INT);
}
/**
@ -118,7 +121,7 @@ abstract class Type implements JsonSerializable
*/
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) {
self::$builtInTypes = array_merge(
Introspection::getTypes(),
self::getInternalTypes()
self::getStandardTypes()
);
}
@ -178,9 +181,44 @@ abstract class Type implements JsonSerializable
*
* @return Type[]
*/
public static function getStandardTypes()
{
return self::getStandardType();
}
/**
* @deprecated Use method getStandardTypes() instead
*
* @return Type[]
*/
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::getInternalTypes() + Introspection::getTypes();
$this->resolvedTypes += Type::getStandardTypes() + Introspection::getTypes();
if ($this->config->typeLoader) {
return;
@ -472,7 +472,7 @@ class Schema
throw new InvariantViolation(implode("\n\n", $this->validationErrors));
}
$internalTypes = Type::getInternalTypes() + Introspection::getTypes();
$internalTypes = Type::getStandardTypes() + Introspection::getTypes();
foreach ($this->getTypeMap() as $name => $type) {
if (isset($internalTypes[$name])) {
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 () {
},
]);
}
}