Almost all of the GraphQL types you define will be object types. Object types
have a name, but most importantly describe their fields.
Example:
var AddressType = new GraphQLObjectType({
name: 'Address',
fields: {
street: { type: GraphQLString },
number: { type: GraphQLInt },
formatted: {
type: GraphQLString,
resolve(obj) {
return obj.number + ' ' + obj.street
}
}
}
});
When two types need to refer to each other, or a type needs to refer to
itself in a field, you can use a function expression (aka a closure or a
thunk) to supply the fields lazily.
Example:
var PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => ({
name: { type: GraphQLString },
bestFriend: { type: PersonType },
})
});
/** * @param ObjectType $object * @param InterfaceType $iface * @throws \Exception */ private function assertObjectImplementsInterface(ObjectType $object, InterfaceType $iface) { $objectFieldMap = $object->getFields(); $ifaceFieldMap = $iface->getFields(); foreach ($ifaceFieldMap as $fieldName => $ifaceField) { Utils::invariant(isset($objectFieldMap[$fieldName]), "\"{$iface}\" expects field \"{$fieldName}\" but \"{$object}\" does not provide it"); /** @var $ifaceField FieldDefinition */ /** @var $objectField FieldDefinition */ $objectField = $objectFieldMap[$fieldName]; Utils::invariant($this->isEqualType($ifaceField->getType(), $objectField->getType()), "{$iface}.{$fieldName} expects type \"{$ifaceField->getType()}\" but " . "{$object}.{$fieldName} provides type \"{$objectField->getType()}"); foreach ($ifaceField->args as $ifaceArg) { /** @var $ifaceArg FieldArgument */ /** @var $objectArg FieldArgument */ $argName = $ifaceArg->name; $objectArg = $objectField->getArg($argName); // Assert interface field arg exists on object field. Utils::invariant($objectArg, "{$iface}.{$fieldName} expects argument \"{$argName}\" but {$object}.{$fieldName} does not provide it."); // Assert interface field arg type matches object field arg type. // (invariant) Utils::invariant($this->isEqualType($ifaceArg->getType(), $objectArg->getType()), "{$iface}.{$fieldName}({$argName}:) expects type \"{$ifaceArg->getType()}\" " . "but {$object}.{$fieldName}({$argName}:) provides " . "type \"{$objectArg->getType()}\""); // Assert argument set invariance. foreach ($objectField->args as $objectArg) { $argName = $objectArg->name; $ifaceArg = $ifaceField->getArg($argName); Utils::invariant($ifaceArg, "{$iface}.{$fieldName} does not define argument \"{$argName}\" but " . "{$object}.{$fieldName} provides it."); } } } }
/** * Determine if output fields should be included. * * @param mixed $objectType * @return boolean */ protected function includeOutputFields(ObjectType $objectType) { $fields = []; foreach ($objectType->getFields() as $name => $field) { $type = $field->getType(); if ($type instanceof ObjectType) { $config = $type->config; if (isset($config['name']) && preg_match('/Connection$/', $config['name'])) { continue; } } $fields[] = $name; } return $fields; }
public function __construct() { $config = ['name' => 'ImageType', 'fields' => ['id' => Types::id(), 'type' => new EnumType(['name' => 'ImageTypeEnum', 'values' => ['USERPIC' => Image::TYPE_USERPIC]]), 'size' => Types::imageSizeEnum(), 'width' => Types::int(), 'height' => Types::int(), 'url' => ['type' => Types::url(), 'resolve' => [$this, 'resolveUrl']], 'fieldWithError' => ['type' => Types::string(), 'resolve' => function () { throw new \Exception("Field with exception"); }], 'nonNullFieldWithError' => ['type' => Types::nonNull(Types::string()), 'resolve' => function () { throw new \Exception("Non-null field with exception"); }]]]; parent::__construct($config); }
public function __construct() { $config = ['name' => 'Query', 'fields' => ['user' => ['type' => Types::user(), 'description' => 'Returns user by id (in range of 1-5)', 'args' => ['id' => Types::nonNull(Types::id())]], 'viewer' => ['type' => Types::user(), 'description' => 'Represents currently logged-in user (for the sake of example - simply returns user with id == 1)'], 'stories' => ['type' => Types::listOf(Types::story()), 'description' => 'Returns subset of stories posted for this blog', 'args' => ['after' => ['type' => Types::id(), 'description' => 'Fetch stories listed after the story with this ID'], 'limit' => ['type' => Types::int(), 'description' => 'Number of stories to be returned', 'defaultValue' => 10]]], 'lastStoryPosted' => ['type' => Types::story(), 'description' => 'Returns last story posted for this blog'], 'deprecatedField' => ['type' => Types::string(), 'deprecationReason' => 'This field is deprecated!'], 'fieldWithException' => ['type' => Types::string(), 'resolve' => function () { throw new \Exception("Exception message thrown in field resolver"); }], 'hello' => Type::string()], 'resolveField' => function ($val, $args, $context, ResolveInfo $info) { return $this->{$info->fieldName}($val, $args, $context, $info); }]; parent::__construct($config); }
public function testDefinesAMutationSchema() { $schema = new Schema($this->blogQuery, $this->blogMutation); $this->assertSame($this->blogMutation, $schema->getMutationType()); $writeMutation = $this->blogMutation->getField('writeArticle'); $this->assertInstanceOf('GraphQL\\Type\\Definition\\FieldDefinition', $writeMutation); $this->assertSame($this->blogArticle, $writeMutation->getType()); $this->assertSame('Article', $writeMutation->getType()->name); $this->assertSame('writeArticle', $writeMutation->name); }
public function __construct() { // Option #2: define type using inheritance, see any other object type for compositional example $config = ['name' => 'ImageType', 'fields' => ['id' => Types::id(), 'type' => new EnumType(['name' => 'ImageTypeEnum', 'values' => ['USERPIC' => Image::TYPE_USERPIC]]), 'size' => Types::imageSizeEnum(), 'width' => Types::int(), 'height' => Types::int(), 'url' => ['type' => Types::url(), 'resolve' => [$this, 'resolveUrl']], 'fieldWithError' => ['type' => Types::string(), 'resolve' => function () { throw new \Exception("Field with exception"); }], 'nonNullFieldWithError' => ['type' => Types::nonNull(Types::string()), 'resolve' => function () { throw new \Exception("Non-null field with exception"); }]]]; parent::__construct($config); }
public function __construct() { $config = ['name' => 'Story', 'fields' => function () { return ['id' => Types::id(), 'author' => Types::user(), 'mentions' => Types::listOf(Types::mention()), 'totalCommentCount' => Types::int(), 'comments' => ['type' => Types::listOf(Types::comment()), 'args' => ['after' => ['type' => Types::id(), 'description' => 'Load all comments listed after given comment ID'], 'limit' => ['type' => Types::int(), 'defaultValue' => 5]]], 'likes' => ['type' => Types::listOf(Types::user()), 'args' => ['limit' => ['type' => Types::int(), 'description' => 'Limit the number of recent likes returned', 'defaultValue' => 5]]], 'likedBy' => ['type' => Types::listOf(Types::user())], 'affordances' => Types::listOf(new EnumType(['name' => 'StoryAffordancesEnum', 'values' => [self::EDIT, self::DELETE, self::LIKE, self::UNLIKE, self::REPLY]])), 'hasViewerLiked' => Types::boolean(), Types::htmlField('body')]; }, 'interfaces' => [Types::node()], 'resolveField' => function ($value, $args, $context, ResolveInfo $info) { if (method_exists($this, $info->fieldName)) { return $this->{$info->fieldName}($value, $args, $context, $info); } else { return $value->{$info->fieldName}; } }]; parent::__construct($config); }
public function __construct() { $config = ['name' => 'Comment', 'fields' => function () { return ['id' => Types::id(), 'author' => Types::user(), 'parent' => Types::comment(), 'isAnonymous' => Types::boolean(), 'replies' => ['type' => Types::listOf(Types::comment()), 'args' => ['after' => Types::int(), 'limit' => ['type' => Types::int(), 'defaultValue' => 5]]], 'totalReplyCount' => Types::int(), Types::htmlField('body')]; }, 'resolveField' => function ($value, $args, $context, ResolveInfo $info) { if (method_exists($this, $info->fieldName)) { return $this->{$info->fieldName}($value, $args, $context, $info); } else { return $value->{$info->fieldName}; } }]; parent::__construct($config); }
public function __construct() { $config = ['name' => 'User', 'description' => 'Our blog authors', 'fields' => function () { return ['id' => Types::id(), 'email' => Types::email(), 'photo' => ['type' => Types::image(), 'description' => 'User photo URL', 'args' => ['size' => Types::nonNull(Types::imageSizeEnum())]], 'firstName' => ['type' => Types::string()], 'lastName' => ['type' => Types::string()], 'lastStoryPosted' => Types::story(), 'fieldWithError' => ['type' => Types::string(), 'resolve' => function () { throw new \Exception("This is error field"); }]]; }, 'interfaces' => [Types::node()], 'resolveField' => function ($value, $args, $context, ResolveInfo $info) { if (method_exists($this, $info->fieldName)) { return $this->{$info->fieldName}($value, $args, $context, $info); } else { return $value->{$info->fieldName}; } }]; parent::__construct($config); }
public function testInputObjectTypeAllowsRecursiveDefinitions() { $called = false; $inputObject = new InputObjectType(['name' => 'InputObject', 'fields' => function () use(&$inputObject, &$called) { $called = true; return ['value' => ['type' => Type::string()], 'nested' => ['type' => $inputObject]]; }]); $someMutation = new ObjectType(['name' => 'SomeMutation', 'fields' => ['mutateSomething' => ['type' => $this->blogArticle, 'args' => ['input' => ['type' => $inputObject]]]]]); $schema = new Schema(['query' => $this->blogQuery, 'mutation' => $someMutation]); $this->assertTrue($called); $this->assertSame($inputObject, $schema->getType('InputObject')); $this->assertEquals(count($inputObject->getFields()), 2); $this->assertSame($inputObject->getField('nested')->getType(), $inputObject); $this->assertSame($someMutation->getField('mutateSomething')->getArg('input')->getType(), $inputObject); }
/** * Complete an Object value by executing all sub-selections. * * @param ExecutionContext $exeContext * @param ObjectType $returnType * @param $fieldNodes * @param ResolveInfo $info * @param array $path * @param $result * @return array|Promise|\stdClass * @throws Error */ private static function completeObjectValue(ExecutionContext $exeContext, ObjectType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result) { // If there is an isTypeOf predicate function, call it with the // current result. If isTypeOf returns false, then raise an error rather // than continuing execution. if (false === $returnType->isTypeOf($result, $exeContext->contextValue, $info)) { throw new Error("Expected value of type {$returnType} but got: " . Utils::getVariableType($result), $fieldNodes); } // Collect sub-fields to execute to complete this value. $subFieldNodes = new \ArrayObject(); $visitedFragmentNames = new \ArrayObject(); foreach ($fieldNodes as $fieldNode) { if (isset($fieldNode->selectionSet)) { $subFieldNodes = self::collectFields($exeContext, $returnType, $fieldNode->selectionSet, $subFieldNodes, $visitedFragmentNames); } } return self::executeFields($exeContext, $returnType, $result, $path, $subFieldNodes); }
/** * This method looks up the field on the given type defintion. * It has special casing for the two introspection fields, __schema * and __typename. __typename is special because it can always be * queried as a field, even in situations where no other fields * are allowed, like on a Union. __schema could get automatically * added to the query type, but that would require mutating type * definitions, which would cause issues. * * @return FieldDefinition */ private static function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName) { $schemaMetaFieldDef = Introspection::schemaMetaFieldDef(); $typeMetaFieldDef = Introspection::typeMetaFieldDef(); $typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef(); if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) { return $schemaMetaFieldDef; } else { if ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) { return $typeMetaFieldDef; } else { if ($fieldName === $typeNameMetaFieldDef->name) { return $typeNameMetaFieldDef; } } } $tmp = $parentType->getFields(); return isset($tmp[$fieldName]) ? $tmp[$fieldName] : null; }
/** * Update the interfaces to know about this implementation. * This is an rare and unfortunate use of mutation in the type definition * implementations, but avoids an expensive "getPossibleTypes" * implementation for Interface types. * * @param ObjectType $impl * @param InterfaceType[] $interfaces */ public static function addImplementationToInterfaces(ObjectType $impl) { foreach ($impl->getInterfaces() as $interface) { $interface->_implementations[] = $impl; } }
public function testAllowsRecursiveDefinitions() { // See https://github.com/webonyx/graphql-php/issues/16 $node = new InterfaceType(['name' => 'Node', 'fields' => ['id' => ['type' => Type::nonNull(Type::id())]]]); $blog = null; $called = false; $user = new ObjectType(['name' => 'User', 'fields' => function () use(&$blog, &$called) { $this->assertNotNull($blog, 'Blog type is expected to be defined at this point, but it is null'); $called = true; return ['id' => ['type' => Type::nonNull(Type::id())], 'blogs' => ['type' => Type::nonNull(Type::listOf(Type::nonNull($blog)))]]; }, 'interfaces' => function () use($node) { return [$node]; }]); $blog = new ObjectType(['name' => 'Blog', 'fields' => function () use($user) { return ['id' => ['type' => Type::nonNull(Type::id())], 'owner' => ['type' => Type::nonNull($user)]]; }, 'interfaces' => function () use($node) { return [$node]; }]); $schema = new Schema(new ObjectType(['name' => 'Query', 'fields' => ['node' => ['type' => $node]]])); $this->assertTrue($called); $this->assertEquals([$node], $blog->getInterfaces()); $this->assertEquals([$node], $user->getInterfaces()); $this->assertNotNull($user->getField('blogs')); $this->assertSame($blog, $user->getField('blogs')->getType()->getWrappedType(true)); $this->assertNotNull($blog->getField('owner')); $this->assertSame($user, $blog->getField('owner')->getType()->getWrappedType(true)); }
/** * Add edge instance. * * @param ObjectType $type * @param string $name * @return void */ public function addEdge(ObjectType $type, $name) { if ($edges = $type->getField('edges')) { $type = $edges->getType()->getWrappedType(); $this->edgeInstances->put($name, $type); } }
public function __construct() { $config = ['fields' => ['b' => Type::string()]]; parent::__construct($config); }
/** * @param FieldContainer $type * @return GQLDefinition\ObjectType */ private function createType(FieldContainer $type) { if (null !== $type->getFields()) { $this->prepareFields($type->getFields()); } $type = new GQLDefinition\ObjectType($type->toMapping()); return $type; }
function __construct($config) { parent::__construct($this->getConfig($config)); }