From e1b4d438db2a6d6fcc50f7e257d3e6587c79a8e7 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Tue, 27 Nov 2018 15:39:20 +0700 Subject: [PATCH] Ability to override internal types (closes #401) --- CHANGELOG.md | 6 +- src/GraphQL.php | 20 ++++- src/Type/Definition/Type.php | 62 ++++++++++++--- src/Type/Schema.php | 4 +- tests/Type/StandardTypesTest.php | 132 +++++++++++++++++++++++++++++++ 5 files changed, 208 insertions(+), 16 deletions(-) create mode 100644 tests/Type/StandardTypesTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1941edf..ed93da4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/GraphQL.php b/src/GraphQL.php index e6fe8c9..497745f 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -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); diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index 9d9a0a8..c349d11 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -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; } /** diff --git a/src/Type/Schema.php b/src/Type/Schema.php index bb17e82..7e480e8 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -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; diff --git a/tests/Type/StandardTypesTest.php b/tests/Type/StandardTypesTest.php new file mode 100644 index 0000000..93f3ceb --- /dev/null +++ b/tests/Type/StandardTypesTest.php @@ -0,0 +1,132 @@ +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 () { + }, + ]); + } +}