ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] #### 定义 Interface Type 接口类型是一个抽象类型,它包含实现该接口的类型必须包含的某一组字段。 interface type 是 GraphQL\Type\Definition\InterfaceType(或其子类)的实例,它的构造函数接收数组配制参数。 ~~~ use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\Type; $character = new InterfaceType([ 'name' => 'Character', 'description' => 'A character in the Star Wars Trilogy', 'fields' => [ 'id' => [ 'type' => Type::nonNull(Type::string()), 'description' => 'The id of the character.', ], 'name' => [ 'type' => Type::string(), 'description' => 'The name of the character.' ] ], 'resolveType' => function ($value) { if ($value->type === 'human') { return MyTypes::human(); } else { return MyTypes::droid(); } } ]); ~~~ #### 配制项 | 名字 | 类型 | 描述 | | --- | --- | --- | | name | `string` | `必填` Schema内这个接口类型的唯一命名 | | fields | `array` | `必填` 接品实现者需要定义的字段列表 | | description | `string` | 类型的纯文本描述(例如通过GraphQL自动生成文档) | | resolveType | `callback` 返回对象类型的实例 | function($value, $context, GraphQL\Type\Definition\ResolveInfo $info)类型解析回调函数,接受从父字段解析器传递的值 $value,并需要返回该 $value 值的具体接口实现者(或实现者的类型名字) | #### 实现接口 对象类型要实现接口,只需要将将其添加到该对象类型定义的interfaces的数组。 ~~~ $humanType = new ObjectType([ 'name' => 'Human', 'fields' => [ 'id' => [ 'type' => Type::nonNull(Type::string()), 'description' => 'The id of the character.', ], 'name' => [ 'type' => Type::string(), 'description' => 'The name of the character.' ] ], 'interfaces' => [ $character ] ]); ~~~ 注意,对象类型必须包括接口的所有字段及对应字段应该具有完全相同的类型(包括非空规范)和参数。 唯一例外的是当对象字段类型相比对应的接口字段类型更明确(参照接口字段的协变返回类型)。 #### 接口字段的协变返回类型 (Covariant return types for interface fields) ~~~ interface A { field1: A } type B implements A { field1: B } ~~~ 基类中某个函数在派生类中可以override,并且返回值得是基类中那个函数返回值的子类(来自java Covariant return types 解释) #### 共享接口字段 Sharing Interface fields 由于实现接口的每个对象类型必须具有接口的字段集 - 在对象类型中重用接口的字段定义通常是有意义的。 ~~~ $humanType = new ObjectType([ 'name' => 'Human', 'interfaces' => [ $character ], 'fields' => [ 'height' => Type::float(), $character->getField('id'), $character->getField('name') ] ]); ~~~ 在这种情况下,字段定义仅创建一次(作为接口类型的一部分),然后由所有接口实现者重新使用。 它可以节省几微秒,千字节,最重要的是保证了接口字段定义和实现者总是保持同步。 共享接口字段在下面两种情况下它能很好的解决问题。 * 如果字段解析器算法 对所有接口实现者是相同的,您可以简单的将字段解析器(resolve )配制项添加到接口本身的字段定义中。 * 如果接口实现者之间对应的接口字段 解析有差异,可以在对象类型配制中指定resolveField选项(当然差异的对象类型字段不能使用$interfaceName->getField('fieldName')这种方式了),并且在那里指定句柄字段解析 (注意:字段定义的resolve选择优先于对象类型定义中的resolveField选项)。 ~~~ $NamedType = new InterfaceType([ 'name' => 'Named', 'fields' => [ 'name' => [ 'type' => Type::string(), 'resolve' => function($v){ return 'named'; } ], ] ]); $DogType = new ObjectType([ 'name' => 'Dog', 'interfaces' => [$NamedType], 'fields' => [ $NamedType->getField('name') ], ]); $CatType = new ObjectType([ 'name' => 'Cat', 'interfaces' => [$NamedType], 'fields' => [ 'name'=>Type::string(), ], 'resolveField' => function($v, $a, $c, $info){ if ($info->fieldName == 'name') { return 'Cat'; } else { return $v; } } ]); ~~~ 上面的类型中,如果查询包含DogType 及 CatType,则DogType的name字段将返回 "named",CatType 则会返回 "Cat" #### 获取数据中接口的角色 在数据获取过程中接口的唯一责任是在 resolveType 中返回给定 value 的具体对象类型。然后字段的解析会委托给这个具体对象类型的解析器。 如果接口的 resolveType 配制省略,程序会循环所有接口实现者,并使用它们的 isTypeOf 回调函数去匹配第一个适合的对象类型(回调函数是否返回true),很显然这样做的效率低于接口单个配制项resolveType调用,因此建议尽可能定义接口的 resolveType 配制项。