LighthouseのInterfaceやUnionを実装する際にちょっとハマったのと、それらに関する情報が調べても全然出てこなかったので記事にしました。
環境
PHP: 7.2.5
Laravel: 7.0
Lighthouse: 4.13
Interface Type
GraphQLのInterfaceはJavaやPHPでいう抽象(abstruct)フィールドと同様に、Interfaceで定義したフィールドを継承したTypeでも提供することを求められます。
またInterfaceを継承したTypeではそれ以外の値は自由に付け足すことが可能です。
GraphQL:Schemas and Types#Interfaces
interface Animal {
id: ID!
name: String!
}
type Cat implements Animal {
id: ID!
name: String!
feed: String
}
type Dog implements Animal {
id: ID!
name: String!
favorite: [DogItem!]!
}
type DogItem {
id: ID!
name: String
}
Iterface Typeを使用するメリットはそれ自体をQueryやMutationの引数や返り値に指定できることです。
例えば以下のような動物の一覧を返すQueryを定義したとします。
type Query {
animals: [Animal!]!
}
これを実行した場合、次のようにCat
とDog
Typeが混ざったレスポンスが得られます。
query {
animals {
id
name
__typename
... on Cat {
feed
}
... on Dog {
favorite {
id
name
}
}
}
}
{
"data": {
"animals": [
{
"id": "1",
"name": "hoge_cat",
"__typename": "Cat",
"feed": "pet_food"
},
{
"id": "2",
"name": "fuga_dog",
"__typename": "Dog",
"favorite": [
{
"id": "1",
"name": "ball"
}
]
},
{
"id": "3",
"name": "piyo_cat",
"__typename": "Cat",
"feed": "cat_food"
}
]
}
}
実装上の注意点
LighthouseでInterfaceを用いて実装するには通常のディレクティブ(@allや@createなど)を使用することはできず、Resolverを使用する必要があります。
$ php artisan lighthouse:query AnimalResolver
type Query {
animals: [Animal!]! @field(resolver: "App\\GraphQL\\Queries\\AnimalResolver@list")
}
またInterfaceを継承するTypeと同名のCat
やDog
のモデルも作成します。
この時、配列や異なる名前のモデルを使用するとbasename() expects parameter 1 to be string, array given
というエラーが発生するので注意しましょう。
$ php artisan make:model Cat
$ php artisan make:model Dog
class AnimalResolver
{
public function list($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo)
{
$hoge_cat = new Cat;
$hoge_cat->id = 1;
$hoge_cat->name = 'hoge_cat';
$hoge_cat->feed = 'pet_food';
$fuga_dog = new Dog;
$fuga_dog->id = 2;
$fuga_dog->name = 'fuga_dog';
// ※Interfaceを継承していない通常のTypeは配列でも問題ない。
$fuga_dog_item = [
'id' => 1,
'name' => 'ball'
];
// モデルを使用する場合は以下のようにする。
// $fuga_dog_item = new DogItem;
// $fuga_dog_item->id = 1;
// $fuga_dog_item->name = 'ball';
$fuga_dog->favorite = [$fuga_dog_item];
$piyo_cat = new Cat;
$piyo_cat->id = 3;
$piyo_cat->name = 'piyo_cat';
$piyo_cat->feed = 'cat_food';
return [
$hoge_cat,
$fuga_dog,
$piyo_cat
];
}
}
Union
Union Typeは他のTypeを単に列挙する抽象Typeです。
ただしInterfaceとは異なり、フィールドを定義することはできません。
GraphQL:Schemas and Types#Union types
union Book = Novel | Comic
type Novel {
novel_title: String!
}
type Comic {
comic_title: String!
}
Queryの実行方法はInterfaceと同様にon
を用いることでそれぞれのTypeのレスポンスを指定することができます。
type Query {
books: [Book!]!
}
query {
books {
__typename
... on Novel {
novel_title
}
... on Comic {
comic_title
}
}
}
{
"data": {
"books": [
{
"__typename": "Novel",
"novel_title": "hoge title"
},
{
"__typename": "Comic",
"comic_title": "comic title"
}
]
}
}
実装上の注意点
Interfaceと同様にUnionを用いる場合には通常のディレクティブを使用できないため、Resolverを使用する必要があります。
またレスポンスのTypeに関してもModelを使用しないとエラーとなるので注意しましょう。
class BookResolver
{
public function list($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo)
{
$novel = new Novel;
$novel->novel_title = 'hoge title';
$comic = new Comic;
$comic->comic_title = 'fuga title';
return [
$novel,
$comic
];
}
}