diff --git a/Tests/DataProvider/TestScalarDataProvider.php b/Tests/DataProvider/TestScalarDataProvider.php index 1d15c78a..e5395bfa 100644 --- a/Tests/DataProvider/TestScalarDataProvider.php +++ b/Tests/DataProvider/TestScalarDataProvider.php @@ -104,7 +104,7 @@ public static function getIdTestData() public static function getDatetimeTestData() { return [ - [null, null, false], + [null, null, true], [new \DateTime('now'), date('Y-m-d H:i:s'), true], ]; } @@ -112,7 +112,7 @@ public static function getDatetimeTestData() public static function getDatetimetzTestData() { return [ - [null, null, false], + [null, null, true], [new \DateTime('now'), date('r'), true], ]; } @@ -120,7 +120,7 @@ public static function getDatetimetzTestData() public static function getDateTestData() { return [ - [null, null, false], + [null, null, true], [new \DateTime('now'), date('Y-m-d'), true], ]; } @@ -130,7 +130,7 @@ public static function getTimestampTestData() { return [ [new \DateTime('now'), time(), true], - [null, null, false], + [null, null, true], ]; } diff --git a/Tests/Issues/Issue90/Issue90Schema.php b/Tests/Issues/Issue90/Issue90Schema.php new file mode 100644 index 00000000..03f0d28a --- /dev/null +++ b/Tests/Issues/Issue90/Issue90Schema.php @@ -0,0 +1,59 @@ +setQuery( + new ObjectType([ + 'name' => 'QueryType', + 'fields' => [ + 'echo' => [ + 'type' => new DateTimeType('Y-m-d H:ia'), + 'args' => [ + 'date' => new DateTimeType('Y-m-d H:ia') + ], + 'resolve' => function ($value, $args, $info) { + + if (isset($args['date'])) { + return $args['date']; + } + + return null; + } + ] + ] + ]) + ); + + $config->setMutation( + new ObjectType([ + 'name' => 'MutationType', + 'fields' => [ + 'echo' => [ + 'type' => new DateTimeType('Y-m-d H:ia'), + 'args' => [ + 'date' => new DateTimeType('Y-m-d H:ia') + ], + 'resolve' => function ($value, $args, $info) { + + if (isset($args['date'])) { + return $args['date']; + } + + return null; + } + ] + ] + ]) + ); + } + +} \ No newline at end of file diff --git a/Tests/Issues/Issue90/Issue90Test.php b/Tests/Issues/Issue90/Issue90Test.php new file mode 100644 index 00000000..e30b2729 --- /dev/null +++ b/Tests/Issues/Issue90/Issue90Test.php @@ -0,0 +1,76 @@ +processPayload("query{ echo(date: \"2016-11-25 09:53am\") }"); + $res = $processor->getResponseData(); + + self::assertCount(1, $res, "Invalid response array received"); //only data expected + + self::assertNotNull($res['data']['echo'], "Invalid echo response received"); + + self::assertEquals("2016-11-25 09:53am", $res['data']['echo']); + } + + public function testQueryDateTimeTypeWithoutParameter() + { + $processor = new Processor(new Issue90Schema()); + $processor->processPayload("query{ echo }"); + $res = $processor->getResponseData(); + + self::assertCount(1, $res, "Invalid response array received"); //only data expected + + self::assertNull($res['data']['echo']); + } + + public function testQueryDateTimeTypeWithNullParameter() + { + $processor = new Processor(new Issue90Schema()); + $processor->processPayload("query{ echo(date: null) }"); + $res = $processor->getResponseData(); + + self::assertCount(1, $res, "Invalid response array received"); //only data expected + + self::assertNull($res['data']['echo'], "Error Quering with explicit date null parameter "); + } + + public function testMutatingDateTimeWithParameter() + { + $schema = new Issue90Schema(); + $processor = new Processor($schema); + $processor->processPayload("mutation{ echo(date: \"2016-11-25 09:53am\") }"); + $res = $processor->getResponseData(); + + self::assertCount(1, $res, "Invalid response array received"); //only data expected + + self::assertNotNull($res['data']['echo'], "Invalid echo response received during mutation of date parameter"); + + self::assertEquals("2016-11-25 09:53am", $res['data']['echo']); + } + + public function testMutatingDateTimeWithExplicitNullParameter() + { + $schema = new Issue90Schema(); + $processor = new Processor($schema); + $processor->processPayload("mutation{ echo(date: null) }"); + $res = $processor->getResponseData(); + + self::assertCount(1, $res, "Invalid response array received"); //only data expected + self::assertNull($res['data']['echo'], "Invalid echo response received during mutation of date parameter with explicit null value"); + } +} \ No newline at end of file diff --git a/Tests/Library/Type/EnumTypeTest.php b/Tests/Library/Type/EnumTypeTest.php index c6f356ac..c6969712 100644 --- a/Tests/Library/Type/EnumTypeTest.php +++ b/Tests/Library/Type/EnumTypeTest.php @@ -120,7 +120,7 @@ public function testNormalCreatingParams() $this->assertEquals($enumType->getNamedType(), $enumType); $this->assertFalse($enumType->isValidValue($enumType)); - $this->assertFalse($enumType->isValidValue(null)); + $this->assertTrue($enumType->isValidValue(null)); $this->assertTrue($enumType->isValidValue(true)); $this->assertTrue($enumType->isValidValue('disable')); diff --git a/Tests/Library/Type/ScalarTypeTest.php b/Tests/Library/Type/ScalarTypeTest.php index f6070c8c..73608ea8 100644 --- a/Tests/Library/Type/ScalarTypeTest.php +++ b/Tests/Library/Type/ScalarTypeTest.php @@ -36,7 +36,7 @@ public function testScalarPrimitives() foreach (call_user_func(['Youshido\Tests\DataProvider\TestScalarDataProvider', $testDataMethod]) as list($data, $serialized, $isValid)) { $this->assertSerialization($scalarType, $data, $serialized); - $this->assertParse($scalarType, $data, $serialized); + $this->assertParse($scalarType, $data, $serialized, $typeName); if ($isValid) { $this->assertTrue($scalarType->isValidValue($data), $typeName . ' validation for :' . serialize($data)); @@ -65,9 +65,13 @@ private function assertSerialization(AbstractScalarType $object, $input, $expect $this->assertEquals($expected, $object->serialize($input), $object->getName() . ' serialize for: ' . serialize($input)); } - private function assertParse(AbstractScalarType $object, $input, $expected) + private function assertParse(AbstractScalarType $object, $input, $expected, $typeName) { - $this->assertEquals($expected, $object->parseValue($input), $object->getName() . ' serialize for: ' . serialize($input)); + $parsed = $object->parseValue($input); + if ($parsed instanceof \DateTime) { + $expected = \DateTime::createFromFormat($typeName == 'datetime' ? 'Y-m-d H:i:s' : 'D, d M Y H:i:s O', $expected); + } + $this->assertEquals($expected, $parsed, $object->getName() . ' parse for: ' . serialize($input)); } } diff --git a/Tests/Schema/InputObjectDefaultValuesTest.php b/Tests/Schema/InputObjectDefaultValuesTest.php index decf1fd2..491a69cf 100644 --- a/Tests/Schema/InputObjectDefaultValuesTest.php +++ b/Tests/Schema/InputObjectDefaultValuesTest.php @@ -22,12 +22,12 @@ public function testDefaultEnum() 'name' => 'InternalStatus', 'values' => [ [ - 'name' => 1, - 'value' => 'ACTIVE' + 'name' => 'ACTIVE', + 'value' => 1, ], [ - 'name' => 0, - 'value' => 'DISABLED' + 'name' => 'DISABLED', + 'value' => 0, ], ] ]); @@ -36,8 +36,8 @@ public function testDefaultEnum() 'name' => 'RootQuery', 'fields' => [ 'stringQuery' => [ - 'type' => new StringType(), - 'args' => [ + 'type' => new StringType(), + 'args' => [ 'statObject' => new InputObjectType([ 'name' => 'StatObjectType', 'fields' => [ @@ -49,12 +49,26 @@ public function testDefaultEnum() ] ]) ], - 'resolve' => function ($source, $args) { + 'resolve' => function ($source, $args) { return sprintf('Result with level %s and status %s', $args['statObject']['level'], $args['statObject']['status'] ); }, ], + 'enumObject' => [ + 'type' => new ObjectType([ + 'name' => 'EnumObject', + 'fields' => [ + 'status' => $enumType + ] + ]), + 'resolve' => function() { + return [ + 'status' => null + ]; + } + ], + ] ]) ]); @@ -62,9 +76,24 @@ public function testDefaultEnum() $processor = new Processor($schema); $processor->processPayload('{ stringQuery(statObject: { level: 1 }) }'); $result = $processor->getResponseData(); + $this->assertEquals(['data' => [ + 'stringQuery' => 'Result with level 1 and status 1' + ]], $result); + + $processor->processPayload('{ stringQuery(statObject: { level: 1, status: DISABLED }) }'); + $result = $processor->getResponseData(); $this->assertEquals(['data' => [ - 'stringQuery' => 'Result with level 1 and status ACTIVE' + 'stringQuery' => 'Result with level 1 and status 0' + ]], $result); + + $processor->processPayload('{ enumObject { status } }'); + $result = $processor->getResponseData(); + + $this->assertEquals(['data' => [ + 'enumObject' => [ + 'status' => null + ] ]], $result); } diff --git a/Tests/Schema/InputParseTest.php b/Tests/Schema/InputParseTest.php index 57bc0be9..d3bc3b86 100644 --- a/Tests/Schema/InputParseTest.php +++ b/Tests/Schema/InputParseTest.php @@ -27,13 +27,13 @@ public function testDateInput($query, $expected) 'stringQuery' => [ 'type' => new StringType(), 'args' => [ - 'from' => new DateTimeType(), + 'from' => new DateTimeType('Y-m-d H:i:s'), 'fromtz' => new DateTimeTzType(), ], 'resolve' => function ($source, $args) { return sprintf('Result with %s date and %s tz', - empty($args['from']) ? 'default' : $args['from'], - empty($args['fromtz']) ? 'default' : $args['fromtz'] + empty($args['from']) ? 'default' : $args['from']->format('Y-m-d H:i:s'), + empty($args['fromtz']) ? 'default' : $args['fromtz']->format('r') ); }, ], diff --git a/examples/js/index.js b/examples/js/index.js index 1ad22ddd..257147f0 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -180,6 +180,12 @@ const blogSchema = new GraphQLSchema({ return [DataProvider.getPost(2), DataProvider.getBanner(3)]; } }, + enumNull: { + type: postStatus, + resolve: () => { + return null; + } + }, scalarList: { type: new GraphQLList(new GraphQLObjectType({ name: 'scalarObject', @@ -224,7 +230,7 @@ const blogSchema = new GraphQLSchema({ }) }); -var query = '{ scalarList(count: 12) { id, cost } }'; +var query = '{ enumNull }'; graphql(blogSchema, query).then(result => { console.log(JSON.stringify(result, null, 5)); process.exit(0); diff --git a/src/Execution/Processor.php b/src/Execution/Processor.php index 6c242614..4ed131f5 100644 --- a/src/Execution/Processor.php +++ b/src/Execution/Processor.php @@ -249,7 +249,7 @@ private function prepareArgumentValue($argumentValue, AbstractType $argumentType foreach($argumentType->getFields() as $field) { /** @var $field Field */ if ($field->getConfig()->has('default')) { - $result[$field->getName()] = $field->getConfig()->get('default'); + $result[$field->getName()] = $field->getType()->getNullableType()->parseInputValue($field->getConfig()->get('default')); } } foreach ($argumentValue->getValue() as $key => $item) { diff --git a/src/Field/AbstractField.php b/src/Field/AbstractField.php index 51c36177..44a68d1f 100644 --- a/src/Field/AbstractField.php +++ b/src/Field/AbstractField.php @@ -26,7 +26,6 @@ abstract class AbstractField implements FieldInterface } protected $isFinal = false; - private $resolveFunctionCache = null; private $nameCache = null; public function __construct(array $config = []) diff --git a/src/Type/AbstractType.php b/src/Type/AbstractType.php index 352461e4..95596653 100644 --- a/src/Type/AbstractType.php +++ b/src/Type/AbstractType.php @@ -53,6 +53,11 @@ public function parseValue($value) return $value; } + public function parseInputValue($value) + { + return $this->parseValue($value); + } + public function serialize($value) { return $value; diff --git a/src/Type/Enum/AbstractEnumType.php b/src/Type/Enum/AbstractEnumType.php index 199c82f0..80a33155 100644 --- a/src/Type/Enum/AbstractEnumType.php +++ b/src/Type/Enum/AbstractEnumType.php @@ -48,6 +48,7 @@ public function getKind() */ public function isValidValue($value) { + if (is_null($value)) return true; foreach ($this->getConfig()->get('values') as $item) { if ($value === $item['name'] || $value === $item['value']) { return true; @@ -84,4 +85,15 @@ public function parseValue($value) return null; } + public function parseInputValue($value) + { + foreach ($this->getConfig()->get('values') as $valueItem) { + if ($value === $valueItem['value']) { + return $valueItem['name']; + } + } + + return null; + } + } diff --git a/src/Type/Scalar/DateTimeType.php b/src/Type/Scalar/DateTimeType.php index 49d27d43..692b806e 100644 --- a/src/Type/Scalar/DateTimeType.php +++ b/src/Type/Scalar/DateTimeType.php @@ -23,33 +23,48 @@ public function getName() return 'DateTime'; } - /** - * @param $value \DateTime - * @return null|string - */ - public function serialize($value) + public function isValidValue($value) { - if ($value === null) { - return null; + if ((is_object($value) && $value instanceof \DateTime) || is_null($value)) { + return true; + } else if (is_string($value)) { + $date = $this->createFromFormat($value); + } else { + $date = null; } - return $value->format($this->format); + return $date ? true : false; } - public function parseValue($value) + public function serialize($value) { - return is_object($value) ? $this->serialize($value) : $value; + $date = null; + + if (is_string($value)) { + $date = $this->createFromFormat($value); + } elseif ($value instanceof \DateTime) { + $date = $value; + } + + return $date ? $date->format($this->format) : null; } - public function isValidValue($value) + public function parseValue($value) { - if (is_object($value)) { - return true; + if (is_string($value)) { + $date = $this->createFromFormat($value); + } elseif ($value instanceof \DateTime) { + $date = $value; + } else { + $date = false; } - $d = \DateTime::createFromFormat($this->format, $value); + return $date ?: null; + } - return $d && $d->format($this->format) == $value; + private function createFromFormat($value) + { + return \DateTime::createFromFormat($this->format, $value); } public function getDescription() diff --git a/src/Type/Scalar/DateTimeTzType.php b/src/Type/Scalar/DateTimeTzType.php index f3bbd375..cd1a6db4 100644 --- a/src/Type/Scalar/DateTimeTzType.php +++ b/src/Type/Scalar/DateTimeTzType.php @@ -10,38 +10,54 @@ class DateTimeTzType extends AbstractScalarType { + private $format = 'D, d M Y H:i:s O'; + public function getName() { return 'DateTimeTz'; } - - /** - * @param $value \DateTime - * @return null|string - */ - public function serialize($value) + public function isValidValue($value) { - if ($value === null) { - return null; + if ((is_object($value) && $value instanceof \DateTime) || is_null($value)) { + return true; + } else if (is_string($value)) { + $date = $this->createFromFormat($value); + } else { + $date = null; } - return $value->format('r'); + return $date ? true : false; } - public function parseValue($value) + public function serialize($value) { - return is_object($value) ? $this->serialize($value) : $value; + $date = null; + + if (is_string($value)) { + $date = $this->createFromFormat($value); + } elseif ($value instanceof \DateTime) { + $date = $value; + } + + return $date ? $date->format($this->format) : null; } - public function isValidValue($value) + public function parseValue($value) { - if (is_object($value)) { - return true; + if (is_string($value)) { + $date = $this->createFromFormat($value); + } elseif ($value instanceof \DateTime) { + $date = $value; + } else { + $date = false; } - $d = \DateTime::createFromFormat('D, d M Y H:i:s O', $value); + return $date ?: null; + } - return $d && $d->format('r') == $value; + private function createFromFormat($value) + { + return \DateTime::createFromFormat($this->format, $value); } public function getDescription() diff --git a/src/Type/Scalar/DateType.php b/src/Type/Scalar/DateType.php index 007d6a8f..b37c92a5 100644 --- a/src/Type/Scalar/DateType.php +++ b/src/Type/Scalar/DateType.php @@ -37,7 +37,7 @@ public function serialize($value) public function isValidValue($value) { - if (is_object($value)) { + if (is_null($value) || is_object($value)) { return true; } diff --git a/src/Type/Scalar/TimestampType.php b/src/Type/Scalar/TimestampType.php index 6923ed48..06a57e28 100644 --- a/src/Type/Scalar/TimestampType.php +++ b/src/Type/Scalar/TimestampType.php @@ -32,7 +32,7 @@ public function serialize($value) public function isValidValue($value) { - if (is_object($value)) { + if (is_null($value) || is_object($value)) { return true; } diff --git a/src/Type/TypeFactory.php b/src/Type/TypeFactory.php index 5e05fecf..25f2550b 100644 --- a/src/Type/TypeFactory.php +++ b/src/Type/TypeFactory.php @@ -26,11 +26,7 @@ public static function getScalarType($type) { if (TypeService::isScalarType($type)) { if (is_object($type)) { - $typeName = $type->getName(); - if (empty(self::$objectsHash[$typeName])) { - self::$objectsHash[$typeName] = $type; - } - return self::$objectsHash[$typeName]; + return $type; } if (empty(self::$objectsHash[$type])) { $name = ucfirst($type);