ralali/graphql-laravel

Laravel 的 PHP GraphQL 包装器

安装: 3

依赖: 0

建议者: 0

安全: 0

星标: 0

关注者: 0

分支: 266

类型:项目

2.1.1 2019-08-31 21:41 UTC

README

Latest Stable Version codecov Build Status Style CI License Get on Slack

注意:这些是 2.* 的文档,请参阅 v1 分支以获取 1.* 文档

使用 Facebook GraphQL 与 Laravel 5.5+。它基于 这里 的 PHP 实现。您可以在 React 博客的 GraphQL 简介 中找到更多关于 GraphQL 的信息,或者您也可以阅读 GraphQL 规范。这是一个正在进行中的工作。

此包与 Eloquent 模型或任何其他数据源兼容。

  • 允许创建作为请求端点的 查询突变
  • 可以为每个查询/突变定义自定义 中间件
  • 查询返回 类型,可以具有自定义的 隐私 设置。
  • 查询的字段将可以选择在 SelectFields 类的帮助下从数据库中 动态 获取。

它提供以下特性和对原始包(由 Folklore 提供)的改进

  • 操作级别的授权
  • 定义字段的可见性的回调(例如,隐藏给未认证的用户)
  • resolve() 中可用 SelectFields 抽象,允许进行高级预加载,从而解决 n+1 问题
  • 支持分页
  • 查询批处理 的服务器端支持
  • 支持文件上传

安装

依赖项

安装

- 使用 Composer 安装包

composer require rebing/graphql-laravel
Laravel 5.5+

1. Laravel 5.5+ 将自动发现包,对于旧版本,请在您的 config/app.php 文件中添加以下服务提供者和别名

Rebing\GraphQL\GraphQLServiceProvider::class,

和别名

'GraphQL' => 'Rebing\GraphQL\Support\Facades\GraphQL',

在您的 config/app.php 文件中。

2. 发布配置文件

$ php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"

3. 查看配置文件

config/graphql.php
Lumen(实验性!)

1. 将以下服务提供者添加到 bootstrap/app.php 文件中

$app->register(Rebing\GraphQL\GraphQLLumenServiceProvider::class);

2. 发布配置文件

$ php artisan graphql:publish

3. 将配置添加到 bootstrap/app.php 文件中 重要: 这需要在注册服务提供者之前完成

$app->configure('graphql');
...
$app->register(Rebing\GraphQL\GraphQLLumenServiceProvider::class);

4. 查看配置文件

config/graphql.php

默认的 GraphiQL 视图使用了全局的 csrf_token() 辅助函数。Lumen 中默认没有这个函数。

为了解决这个问题

  • 指向您的本地 GraphiQL 视图:将 graphql.view 更改为 'vendor/graphql/graphiql'
  • 修改您的 resources/views/vendor/graphql/graphiql.php 文件并删除调用

用法

模式

定义GraphQL端点需要使用模式。您可以在全局中间件之外,为多个模式分配不同的中间件。例如

'schema' => 'default_schema',

'schemas' => [
    'default' => [
        'query' => [
            'example_query' => ExampleQuery::class,
        ],
        'mutation' => [
            'example_mutation'  => ExampleMutation::class,
        ],
    ],
    'user' => [
        'query' => [
            'profile' => App\GraphQL\Queries\ProfileQuery::class
        ],
        'mutation' => [
        
        ],
        'middleware' => ['auth'],
    ],
],

创建查询

首先,您需要创建一个类型。只有在指定关系时,Eloquent模型才是必需的。

注意:如果它是一个非数据库字段或不是关系,则需要使用selectable

<?php

namespace App\GraphQL\Types;

use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;

class UserType extends GraphQLType
{    
    protected $attributes = [
        'name'          => 'User',
        'description'   => 'A user',
        'model'         => User::class,
    ];

    public function fields(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The id of the user',
                // Use 'alias', if the database column is different from the type name.
                // This is supported for discrete values as well as relations.
                // - you can also use `DB::raw()` to solve more complex issues
                // - or a callback returning the value (string or `DB::raw()` result)
                'alias' => 'user_id',
            ],
            'email' => [
                'type' => Type::string(),
                'description' => 'The email of user',
            ],
            // Uses the 'getIsMeAttribute' function on our custom User model
            'isMe' => [
                'type' => Type::boolean(),
                'description' => 'True, if the queried user is the current user',
                'selectable' => false, // Does not try to query this from the database
            ]
        ];
    }

    // If you want to resolve the field yourself, you can declare a method
    // with the following format resolve[FIELD_NAME]Field()
    protected function resolveEmailField($root, $args)
    {
        return strtolower($root->email);
    }    
}

将类型添加到config/graphql.php配置文件

'types' => [
    'user' => App\GraphQL\Types\UserType::class
]

您也可以使用GraphQL外观,在服务提供器等地方添加类型。

GraphQL::addType(\App\GraphQL\Types\UserType::class, 'user');

然后,您需要定义一个返回此类型(或列表)的查询。您还可以指定可以在resolve方法中使用的参数。

<?php

namespace App\GraphQL\Queries;

use Closure;
use App\User;
use Rebing\GraphQL\Support\Facades\GraphQL;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;

class UsersQuery extends Query
{
    protected $attributes = [
        'name' => 'Users query'
    ];

    public function type(): Type
    {
        return Type::listOf(GraphQL::type('user'));
    }

    public function args(): array
    {
        return [
            'id' => ['name' => 'id', 'type' => Type::string()],
            'email' => ['name' => 'email', 'type' => Type::string()]
        ];
    }

    public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        if (isset($args['id'])) {
            return User::where('id' , $args['id'])->get();
        }

        if (isset($args['email'])) {
            return User::where('email', $args['email'])->get();
        }

        return User::all();
    }
}

将查询添加到config/graphql.php配置文件

'schemas' => [
    'default' => [
        'query' => [
            'users' => App\GraphQL\Queries\UsersQuery::class
        ],
        // ...
    ]
]

就这样。您应该能够通过向URL /graphql(或您在配置中选择的任何内容)发送请求来查询GraphQL。尝试以下query输入的GET请求

query FetchUsers {
    users {
        id
        email
    }
}

例如,如果您使用homestead

http://homestead.app/graphql?query=query+FetchUsers{users{id,email}}

创建突变

变异就像任何其他查询一样。它接受参数(这些参数将被用于执行变异)并返回一定类型的对象。

例如,一个用于更新用户密码的变异。首先,您需要定义变异

<?php

namespace App\GraphQL\Mutations;

use CLosure;
use App\User;
use GraphQL;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Mutation;

class UpdateUserPasswordMutation extends Mutation
{
    protected $attributes = [
        'name' => 'UpdateUserPassword'
    ];

    public function type(): Type
    {
        return GraphQL::type('user');
    }

    public function args(): array
    {
        return [
            'id' => ['name' => 'id', 'type' => Type::nonNull(Type::string())],
            'password' => ['name' => 'password', 'type' => Type::nonNull(Type::string())]
        ];
    }

    public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        $user = User::find($args['id']);
        if(!$user) {
            return null;
        }

        $user->password = bcrypt($args['password']);
        $user->save();

        return $user;
    }
}

如您在resolve()方法中看到的那样,您使用参数来更新模型并返回它。

然后,您应该将变异添加到config/graphql.php配置文件

'schemas' => [
    'default' => [
        'mutation' => [
            'updateUserPassword' => App\GraphQL\Mutations\UpdateUserPasswordMutation::class
        ],
        // ...
    ]
]

然后,您应该能够在端点使用以下查询来执行变异

mutation users {
    updateUserPassword(id: "1", password: "newpassword") {
        id
        email
    }
}

如果您使用homestead

http://homestead.app/graphql?query=mutation+users{updateUserPassword(id: "1", password: "newpassword"){id,email}}

向变异添加验证

可以在变异中添加验证规则。它使用Laravel的Validator来对$args执行验证。

在创建变异时,您可以添加一个方法来定义应用的验证规则,如下所示

<?php

namespace App\GraphQL\Mutations;

use Closure;
use App\User;
use GraphQL;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;

class UpdateUserEmailMutation extends Mutation
{
    protected $attributes = [
        'name' => 'UpdateUserEmail'
    ];

    public function type(): Type
    {
        return GraphQL::type('user');
    }

    public function args(): array
    {
        return [
            'id' => ['name' => 'id', 'type' => Type::string()],
            'email' => ['name' => 'email', 'type' => Type::string()]
        ];
    }

    protected function rules(array $args = []): array
    {
        return [
            'id' => ['required'],
            'email' => ['required', 'email']
        ];
    }

    public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        $user = User::find($args['id']);
        if (!$user) {
            return null;
        }

        $user->email = $args['email'];
        $user->save();

        return $user;
    }
}

或者,您可以在每个参数上定义规则

<?php
class UpdateUserEmailMutation extends Mutation
{
    //...

    public function args(): array
    {
        return [
            'id' => [
                'name' => 'id',
                'type' => Type::string(),
                'rules' => ['required']
            ],
            'email' => [
                'name' => 'email',
                'type' => Type::string(),
                'rules' => ['required', 'email']
            ]
        ];
    }

    //...
}

在执行变异时,它将返回发生的任何验证错误。由于GraphQL规范定义了错误的一定格式,验证错误被添加到错误对象中,作为额外的validation属性。要查找验证错误,您应该检查具有message等于'validation'的错误,然后validation属性将包含Laravel Validator返回的正常错误消息

{
    "data": {
        "updateUserEmail": null
    },
    "errors": [
        {
            "message": "validation",
            "locations": [
                {
                    "line": 1,
                    "column": 20
                }
            ],
            "validation": {
                "email": [
                    "The email is invalid."
                ]
            }
        }
    ]
}

返回的验证错误可以通过覆盖变异上的validationErrorMessages方法进行自定义。该方法应该返回一个数组,其中包含与Laravel验证相同的自定义验证消息。例如,要检查email参数是否与任何现有数据冲突,您可以执行以下操作

注意:键应该以field_name.validator_type格式,以便您可以针对验证类型返回特定错误。

public function validationErrorMessages(array $args = []): array
{
    return [
        'name.required' => 'Please enter your full name',
        'name.string' => 'Your name must be a valid string',
        'email.required' => 'Please enter your email address',
        'email.email' => 'Please enter a valid email address',
        'email.exists' => 'Sorry, this email address is already in use',                     
    ];
}

文件上传

此库提供符合规范的中间件,规范位于https://github.com/jaydenseric/graphql-multipart-request-spec

您必须首先将\Rebing\GraphQL\Support\UploadType添加到您的config/graphql模式类型定义中

'types' => [
    \Rebing\GraphQL\Support\UploadType::class,
],

您需要发送请求为multipart/form-data,这是相关的。

警告:当您上传文件时,Laravel将使用FormRequest - 这意味着更改请求的中间件将没有任何效果。

<?php

namespace App\GraphQL\Mutations;

use Closure;
use GraphQL;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Mutation;

class UserProfilePhotoMutation extends Mutation
{
    protected $attributes = [
        'name' => 'UpdateUserProfilePhoto'
    ];

    public function type(): Type
    {
        return GraphQL::type('User');
    }

    public function args(): array
    {
        return [
            'profilePicture' => [
                'name' => 'profilePicture',
                'type' => GraphQL::type('Upload'),
                'rules' => ['required', 'image', 'max:1500'],
            ],
        ];
    }

    public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields)
    {
        $file = $args['profilePicture'];

        // Do something with file here...
    }
}

注意:您可以使用Altair测试您的文件上传实现,方法如此处所述。

授权

对于类似于Laravel的请求(或中间件)功能的授权,我们可以在查询或变异中覆盖authorize()函数。以下是一个Laravel的'auth'中间件示例

use Auth;

class UsersQuery extends Query
{
    public function authorize(array $args): bool
    {
        // true, if logged in
        return ! Auth::guest();
    }

    // ...
}

或者我们可以利用通过GraphQL查询传递的参数

use Auth;

class UsersQuery extends Query
{
    public function authorize(array $args): bool
    {
        if (isset($args['id'])) {
            return Auth::id() == $args['id'];
        }

        return true;
    }

    // ...
}

隐私

您可以为每个类型的字段设置自定义的隐私属性。如果一个字段不允许,则返回 null。例如,如果您希望用户的电子邮件只对自己可见

class UserType extends GraphQLType
{
    // ...

    public function fields(): array
    {
        return [
            'id' => [
                'type'          => Type::nonNull(Type::string()),
                'description'   => 'The id of the user'
            ],
            'email' => [
                'type'          => Type::string(),
                'description'   => 'The email of user',
                'privacy'       => function(array $args): bool {
                    return $args['id'] == Auth::id();
                }
            ]
        ];
    }

    // ...

}

或者您可以创建一个扩展抽象GraphQL隐私类的类

use Auth;
use Rebing\GraphQL\Support\Privacy;

class MePrivacy extends Privacy
{
    public function validate(array $queryArgs): bool
    {
        return $args['id'] == Auth::id();
    }
}
use MePrivacy;

class UserType extends GraphQLType
{

    // ...

    public function fields(): array
    {
        return [
            'id' => [
                'type'          => Type::nonNull(Type::string()),
                'description'   => 'The id of the user'
            ],
            'email' => [
                'type'          => Type::string(),
                'description'   => 'The email of user',
                'privacy'       => MePrivacy::class,
            ]
        ];
    }

    // ...

}

查询变量

GraphQL允许您在查询中使用变量,这样您就无需“硬编码”值。这样做的方式如下

query FetchUserByID($id: String)
{
    user(id: $id) {
        id
        email
    }
}

当您查询GraphQL端点时,您可以通过传递一个params(或您在配置中定义的任何参数)参数。

http://homestead.app/graphql?query=query+FetchUserByID($id:Int){user(id:$id){id,email}}&params={"id":123}

请注意,您的客户端框架可能使用与params不同的参数名。您可以通过调整graphql.php配置文件中的params_key来自定义任何客户端使用的参数名称。

自定义字段

如果您想在多个类型中重复使用字段,也可以将其定义为类。

<?php

namespace App\GraphQL\Fields;

use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Field;

class PictureField extends Field
{        
    protected $attributes = [
        'description'   => 'A picture',
    ];

    public function type(): Type
    {
        return Type::string();
    }

    public function args(): array
    {
        return [
            'width' => [
                'type' => Type::int(),
                'description' => 'The width of the picture'
            ],
            'height' => [
                'type' => Type::int(),
                'description' => 'The height of the picture'
            ]
        ];
    }

    protected function resolve($root, $args)
    {
        $width = isset($args['width']) ? $args['width']:100;
        $height = isset($args['height']) ? $args['height']:100;

        return 'http://placehold.it/'.$width.'x'.$height;
    }
}

然后您可以在类型声明中使用它

<?php

namespace App\GraphQL\Types;

use App\GraphQL\Fields\PictureField;
use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;

class UserType extends GraphQLType
{
    protected $attributes = [
        'name'          => 'User',
        'description'   => 'A user',
        'model'         => User::class,
    ];

    public function fields(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The id of the user'
            ],
            'email' => [
                'type' => Type::string(),
                'description' => 'The email of user'
            ],
            //Instead of passing an array, you pass a class path to your custom field
            'picture' => PictureField::class
        ];
    }
}

预加载关系

传递给查询的解析方法的第五个参数是一个返回Rebing\GraphQL\Support\SelectFields实例的闭包,您可以使用它来检索请求中的键。以下是一个使用此信息来预加载相关Eloquent模型的示例。

这样,数据库中只会查询所需的字段。

闭包接受一个可选参数,用于分析查询的深度。

您的查询可能如下所示

<?php

namespace App\GraphQL\Queries;

use Closure;
use App\User;
use GraphQL;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\SelectFields;
use Rebing\GraphQL\Support\Query;

class UsersQuery extends Query
{
    protected $attributes = [
        'name' => 'Users query'
    ];

    public function type(): Type
    {
        return Type::listOf(GraphQL::type('user'));
    }

    public function args(): array
    {
        return [
            'id' => ['name' => 'id', 'type' => Type::string()],
            'email' => ['name' => 'email', 'type' => Type::string()]
        ];
    }

    public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields)
    {
        // $info->getFieldSelection($depth = 3);

        // If your GraphQL query exceeds the default nesting query, you can increase it here:
        // $fields = $getSelectFields(11);

        /** @var SelectFields $fields */
        $fields = $getSelectFields();
        $select = $fields->getSelect();
        $with = $fields->getRelations();

        $users = User::select($select)->with($with);

        return $users->get();
    }
}

您的用户类型可能如下所示。profileposts关系也必须在UserModel的关系中存在。如果某些字段对于加载关系或验证等是必需的,那么您可以定义一个always属性,它将给定的属性添加到选择中。

该属性可以是逗号分隔的字符串或包含始终包含的属性的数组。

<?php

namespace App\GraphQL\Types;

use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;

class UserType extends GraphQLType
{
    /**
     * @var array
     */
    protected $attributes = [
        'name'          => 'User',
        'description'   => 'A user',
        'model'         => User::class,
    ];

    /**
    * @return array
    */
    public function fields(): array
    {
        return [
            'uuid' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The uuid of the user'
            ],
            'email' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The email of user'
            ],
            'profile' => [
                'type' => GraphQL::type('Profile'),
                'description' => 'The user profile',
            ],
            'posts' => [
                'type' => Type::listOf(GraphQL::type('Post')),
                'description' => 'The user posts',
                // Can also be defined as a string
                'always' => ['title', 'body'],
            ]
        ];
    }
}

在此阶段,我们有一个符合任何模型的预期的profilepost类型

class ProfileType extends GraphQLType
{
    protected $attributes = [
        'name'          => 'Profile',
        'description'   => 'A user profile',
        'model'         => UserProfileModel::class,
    ];

    public function fields(): array
    {
        return [
            'name' => [
                'type' => Type::string(),
                'description' => 'The name of user'
            ]
        ];
    }
}
class PostType extends GraphQLType
{
    protected $attributes = [
        'name'          => 'Post',
        'description'   => 'A post',
        'model'         => PostModel::class,
    ];

    public function fields(): array
    {
        return [
            'title' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The title of the post'
            ],
            'body' => [
                'type' => Type::string(),
                'description' => 'The body the post'
            ]
        ];
    }
}

类型关系查询

您还可以通过Eloquent的查询构建器指定与关系一起包含的query

class UserType extends GraphQLType
{

    // ...

    public function fields(): array
    {
        return [
            // ...

            // Relation
            'posts' => [
                'type'          => Type::listOf(GraphQL::type('post')),
                'description'   => 'A list of posts written by the user',
                'args'          => [
                    'date_from' => [
                        'type' => Type::string(),
                    ],
                 ],
                // $args are the local arguments passed to the relation
                // $query is the relation builder object
                // $ctx is the GraphQL context (can be customized by overriding `\Rebing\GraphQL\GraphQLController::queryContext`
                'query'         => function(array $args, $query, $ctx) {
                    return $query->where('posts.created_at', '>', $args['date_from']);
                }
            ]
        ];
    }
}

分页

如果查询或突变返回一个PaginationType,则将使用分页。请注意,您必须手动处理限制和页面值。

class PostsQuery extends Query
{
    public function type(): \GraphQL\Type\Definition\Type
    {
        return GraphQL::paginate('posts');
    }

    // ...

    public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields)
    {
        $fields = $getSelectFields();
        return Post
            ::with($fields->getRelations())
            ->select($fields->getSelect())
            ->paginate($args['limit'], ['*'], 'page', $args['page']);
    }
}

查询posts(limit:10,page:1){data{id},total,per_page}可能返回

{
    "data": {
        "posts: [
            "data": [
                {"id": 3},
                {"id": 5},
                ...
            ],
            "total": 21,
            "per_page": 10
        ]
    }
}

请注意,当您请求分页资源时,需要添加额外的'data'对象,因为返回的数据在返回的分页元数据同一级别上提供了一个数据对象,其中包含分页资源。

批处理

您可以通过将它们分组在一起一次发送多个查询(或突变)。

POST
{
    query: "query postsQuery { posts { id, comment, author_id } }"
}

POST
{
    query: "mutation storePostMutation($comment: String!) { store_post(comment: $comment) { id } }",
    variables: { "comment": "Hi there!" }
}

因此,您不需要创建两个HTTP请求,而是可以将其批处理为一个

POST
[
    {
        query: "query postsQuery { posts { id, comment, author_id } }"
    },
    {
        query: "mutation storePostMutation($comment: String!) { store_post(comment: $comment) { id } }",
        variables: { "comment": "Hi there!" }
    }
]

对于一次发送多个请求的系统,这可以通过将将在一定时间间隔内进行的查询批处理在一起来提高性能。

有一些工具可以帮助您完成这项工作,并为您处理批处理,例如 Apollo

标量类型

GraphQL自带了用于字符串、整数、布尔值等的内置标量类型。您可以创建自定义标量类型以用于特殊字段。

一个例子可以是链接:而不是使用Type::string(),您可以创建一个标量类型Link,并使用GraphQL::type('Link')引用它。

好处是

  • 一个专门的描述,这样您可以给字段赋予比仅仅叫它字符串类型更多的意义/目的
  • 显式的转换逻辑用于以下步骤
    • 将内部逻辑转换为序列化的GraphQL输出(《serialize》)
    • 查询/字段输入参数转换(《parseLiteral》)
    • 当作为变量传递给您的查询(《parseValue》)时

这意味着您可以在这些方法中添加验证逻辑,以确保传递/接收的值是例如一个真正的链接。

标量类型必须实现所有方法;您可以使用artisan make:graphql:scalar <typename>快速开始。然后只需将标量添加到您的现有类型中。

对于更高级的使用,请参阅有关标量类型的官方文档此处

枚举

枚举类型是一种特殊的标量,其值被限制在特定的允许值集合中。关于枚举的更多信息这里

首先,将枚举作为GraphQLType类的扩展创建一个。

<?php

namespace App\GraphQL\Enums;

use Rebing\GraphQL\Support\EnumType;

class EpisodeEnum extends EnumType
{
    protected $attributes = [
        'name' => 'Episode',
        'description' => 'The types of demographic elements',
        'values' => [
            'NEWHOPE' => 'NEWHOPE',
            'EMPIRE' => 'EMPIRE',
            'JEDI' => 'JEDI',
        ],
    ];
}

注意:$attributes['values']数组中,键是GraphQL客户端可以从中选择的枚举值,而值是服务器将接收的内容(枚举将被解析为的内容)。

graphql.php配置文件的types数组中注册枚举。

'types' => [
    'EpisodeEnum' => EpisodeEnum::class
];

然后像这样使用它:

<?php

namespace App\GraphQL\Types;

use Rebing\GraphQL\Support\Type as GraphQLType;

class TestType extends GraphQLType
{
    public function fields(): array
    {
        return [
            'episode_type' => [
                'type' => GraphQL::type('EpisodeEnum')
            ]
        ];
    }
}

联合

联合体是一种抽象类型,它简单地枚举其他对象类型。联合体类型的值实际上是包含对象类型之一的价值。

当您需要在同一查询中返回不相关的类型时很有用。例如,当实现多个不同实体的搜索时。

定义UnionType的示例

<?php

namespace App\GraphQL\Unions;

use App\Post;
use GraphQL;
use Rebing\GraphQL\Support\UnionType;

class SearchResultUnion extends UnionType
{
    protected $attributes = [
        'name' => 'SearchResult',
    ];

    public function types(): array
    {
        return [
            GraphQL::type('Post'),
            GraphQL::type('Episode'),
        ];
    }

    public function resolveType($value)
    {
        if ($value instanceof Post) {
            return GraphQL::type('Post');
        } elseif ($value instanceof Episode) {
            return GraphQL::type('Episode');
        }
    }
}

接口

您可以使用接口来抽象一组字段。关于接口的更多信息这里

接口的实现

<?php

namespace App\GraphQL\Interfaces;

use GraphQL;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\InterfaceType;

class CharacterInterface extends InterfaceType
{
    protected $attributes = [
        'name' => 'Character',
        'description' => 'Character interface.',
    ];

    public function fields(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::int()),
                'description' => 'The id of the character.'
            ],
            'name' => Type::string(),
            'appearsIn' => [
                'type' => Type::nonNull(Type::listOf(GraphQL::type('Episode'))),
                'description' => 'A list of episodes in which the character has an appearance.'
            ],
        ];
    }

    public function resolveType($root)
    {
        // Use the resolveType to resolve the Type which is implemented trough this interface
        $type = $root['type'];
        if ($type === 'human') {
            return GraphQL::type('Human');
        } elseif  ($type === 'droid') {
            return GraphQL::type('Droid');
        }
    }
}

实现接口的类型

<?php

namespace App\GraphQL\Types;

use GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
use GraphQL\Type\Definition\Type;

class HumanType extends GraphQLType
{
    protected $attributes = [
        'name' => 'Human',
        'description' => 'A human.'
    ];

    public function fields(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::int()),
                'description' => 'The id of the human.',
            ],
            'name' => Type::string(),
            'appearsIn' => [
                'type' => Type::nonNull(Type::listOf(GraphQL::type('Episode'))),
                'description' => 'A list of episodes in which the human has an appearance.'
            ],
            'totalCredits' => [
                'type' => Type::nonNull(Type::int()),
                'description' => 'The total amount of credits this human owns.'
            ]
        ];
    }

    public function interfaces(): array
    {
        return [
            GraphQL::type('Character')
        ];
    }
}

共享接口字段

由于您经常需要在具体类型中重复许多接口的字段定义,因此共享接口的定义是有意义的。您可以使用方法getField(string fieldName): FieldDefinition访问和重用特定的接口字段。要获取所有字段作为数组,请使用getFields(): array

有了这个,您可以将HumanType类的fields方法写成这样:

public function fields(): array
{
    $interface = GraphQL::type('Character');

    return [
        $interface->getField('id'),
        $interface->getField('name'),
        $interface->getField('appearsIn'),

        'totalCredits' => [
            'type' => Type::nonNull(Type::int()),
            'description' => 'The total amount of credits this human owns.'
        ]
    ];
}

或通过使用getFields方法

public function fields(): array
{
    $interface = GraphQL::type('Character');

    return array_merge($interface->getFields(), [
        'totalCredits' => [
            'type' => Type::nonNull(Type::int()),
            'description' => 'The total amount of credits this human owns.'
        ]
    ]);
}

输入对象

输入对象类型允许您创建复杂的输入。字段没有参数或解析选项,其类型必须是输入类型。如果您想验证输入数据,可以添加规则选项。关于输入对象的更多信息这里

首先,将输入对象类型作为GraphQLType类的扩展创建一个。

<?php

namespace App\GraphQL\InputObject;

use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\InputType;

class ReviewInput extends InputType
{
    protected $attributes = [
        'name' => 'ReviewInput',
        'description' => 'A review with a comment and a score (0 to 5)'
    ];

    public function fields(): array
    {
        return [
            'comment' => [
                'name' => 'comment',
                'description' => 'A comment (250 max chars)',
                'type' => Type::string(),
                // You can define Laravel Validation here
                'rules' => ['max:250']
            ],
            'score' => [
                'name' => 'score',
                'description' => 'A score (0 to 5)',
                'type' => Type::int(),
                'rules' => ['min:0', 'max:5']
            ]
        ];
    }
}

graphql.php配置文件的types数组中注册输入对象。

'types' => [
    'ReviewInput' => ReviewInput::class
];

然后在突变中使用它,如下所示:

// app/GraphQL/Type/TestMutation.php
class TestMutation extends GraphQLType {

    public function args(): array
    {
        return [
            'review' => [
                'type' => GraphQL::type('ReviewInput')
            ]
        ]
    }

}

JSON 列

当在数据库中使用JSON列时,该字段不会被定义为“关系”,而是一个包含嵌套数据的简单列。要获取一个不是数据库关系的嵌套对象,请在使用Type时使用is_relation属性。

class UserType extends GraphQLType
{

    // ...

    public function fields(): array
    {
        return [
            // ...

            // JSON column containing all posts made by this user
            'posts' => [
                'type'          => Type::listOf(GraphQL::type('post')),
                'description'   => 'A list of posts written by the user',
                // Now this will simply request the "posts" column, and it won't
                // query for all the underlying columns in the "post" object
                // The value defaults to true
                'is_relation' => false
            ]
        ];
    }

    // ...
}

字段弃用

有时您可能希望弃用字段,但仍然需要维护向后兼容性,直到客户端完全停止使用该字段。您可以使用指令来弃用字段。如果您将deprecationReason添加到字段属性中,它将在GraphQL文档中标记为弃用。您可以使用Apollo Engine在客户端验证模式。

<?php

namespace App\GraphQL\Types;

use App\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;

class UserType extends GraphQLType
{    
    protected $attributes = [
        'name'          => 'User',
        'description'   => 'A user',
        'model'         => User::class,
    ];

    public function fields(): array
    {
        return [
            'id' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The id of the user',
            ],
            'email' => [
                'type' => Type::string(),
                'description' => 'The email of user',
            ],
            'address' => [
                'type' => Type::string(),
                'description' => 'The address of user',
                'deprecationReason' => 'Deprecated due to address field split'
            ],
            'address_line_1' => [
                'type' => Type::string(),
                'description' => 'The address line 1 of user',
            ],
            'address_line_2' => [
                'type' => Type::string(),
                'description' => 'The address line 2 of user',
            ],
        ];
    }
}

默认字段解析器

您可以使用配置选项defaultFieldResolver覆盖由底层webonyx/graphql-php库提供的默认字段解析器。

您可以为它定义任何有效的可调用项(静态类方法、闭包等)

'defaultFieldResolver' => [Your\Klass::class, 'staticMethod'],

接收到的参数是常规的“解析”函数签名。

指南

从 v1 升级到 v2

尽管版本2建立在相同的代码库之上,并且没有彻底改变库本身的工作方式,但许多事情都得到了改进,有时导致了不兼容的更改。

  • 步骤0:进行备份!
  • 重新发布配置文件以了解所有新设置
  • 解析器顺序和参数/类型已更改
    • 之前: resolve($root, $array, SelectFields $selectFields, ResolveInfo $info)
    • 之后: resolve($root, $array, $context, ResolveInfo $info, Closure $getSelectFields)
    • 如果您现在想使用 SelectFields,您必须首先请求它: $selectFields = $getSelectFields();。这样做的主要原因是性能。SelectFields 是一个可选功能,但它需要消耗资源来遍历 GraphQL 请求的 AST 并内省所有类型以应用其配置。在过去,它始终被构建,因此即使没有请求也会消耗资源。这已经被更改为显式形式。
  • 许多方法签名声明已更改以提高类型安全,必须进行适配
    • 字段方法的签名已更改
      • public function fields()
      • public function fields(): array
    • toType 方法的签名已更改
      • public function toType()
      • public function toType(): \GraphQL\Type\Definition\Type
    • getFields 方法的签名已更改
      • public function getFields()
      • public function getFields(): array
    • interfaces 方法的签名已更改
      • public function interfaces()
      • public function interfaces(): array
    • types 方法的签名已更改
      • public function types()
      • public function types(): array
    • type 方法的签名已更改
      • public function type()
      • public function type(): \GraphQL\Type\Definition\Type
    • args 方法的签名已更改
      • public function args()
      • public function args(): array
    • queryContext 方法的签名已更改
      • protected function queryContext($query, $variables, $schema)
      • protected function queryContext()
    • 控制器方法 query 的签名已更改
      • function query($query, $variables = [], $opts = [])
      • function query(string $query, ?array $variables = [], array $opts = []): array
    • 如果您使用自定义标量类型
      • parseLiteral 方法的签名已更改(由于 webonxy 库的升级)
        • public function parseLiteral($ast)
        • public function parseLiteral($valueNode, ?array $variables = null)
  • 如果要将 UploadType 添加到您的模式中的 types,您必须手动添加。已删除 ::getInstance() 方法,您可以通过 GraphQL::type('Upload') 类似于其他类型来引用它。
  • 遵循 Laravel 习惯,使用复数命名空间(例如,新的查询放在 App\GraphQL\Queries,而不是 App\GraphQL\Query);相应的 make 命令已调整。这不会破坏任何现有代码,但生成的代码将使用新模式。
  • 请务必阅读变更日志以获取更多详细信息

从传说迁移

https://github.com/folkloreinc/laravel-graphql,之前也称为 https://github.com/Folkloreatelier/laravel-graphql

这两个代码库非常相似,并且根据您的自定义程度,迁移可能会非常快速。

注意:此迁移是以本库的 2.* 版本为基础编写的。

以下列表不是万无一失的,但应作为指南。如果您不需要执行某些步骤,则不会出现错误。

在继续之前,请备份!

  • composer remove folklore/graphql
  • 如果您有自定义 ServiceProvider 或手动包含它,请将其删除。重点是现有 GraphQL 代码不应被触发执行。
  • composer require rebing/graphql-laravel
  • 发布 config/graphql.php 并进行适配(前缀、中间件、模式、类型、突变、查询、安全设置、graphiql)
    • 已删除设置
      • 域名
      • 解析器
    • schema(默认schema)已重命名为default_schema
    • middleware_schema不存在了,现在它在schema.<name>.middleware中定义
  • 更改命名空间引用
    • Folklore\
    • Rebing\
  • 查看从v1升级到v2的升级指南,其中包含所有函数签名更改
  • 特性ShouldValidate不再存在;提供的功能已集成到Field
  • 查询/突变中解析方法的第一个参数现在是null(之前默认为空数组)

性能考虑

类型懒加载

类型懒加载是一种提高启动性能的方法。

如果您使用别名声明类型,则不支持。如果不是这种情况,您可以将lazyload_types设置为true以启用它。

不支持懒加载的别名示例

即,您不能有一个具有$name属性example的查询类ExampleQuery,但使用不同的一个注册它;这不会工作

'query' => [
    'aliasedEXample' => ExampleQuery::class,
],