tdmhai / graphql-laravel
Laravel 对 PHP GraphQL 的封装
Requires
- php: >= 7.1
- ext-json: *
- illuminate/contracts: 5.5.*|5.6.*|5.7.*|5.8.*|^6.0
- illuminate/support: 5.5.*|5.6.*|5.7.*|5.8.*|^6.0
- webonyx/graphql-php: ^0.13
Requires (Dev)
- mockery/mockery: ^1.2
- nunomaduro/larastan: 0.4.2
- orchestra/testbench: 3.5.*|3.6.*|3.7.*|3.8.*|3.9.*|4.0.*
- phpunit/phpunit: ^5.5|~6.0|~7.0|~8.0
This package is auto-updated.
Last update: 2024-09-25 15:43:42 UTC
README
注意:这是 2.* 版本的文档,请参阅 v1 分支以获取 1.* 版本的文档
使用 Facebook GraphQL,支持 Laravel 5.5+。它基于此处的 PHP 实现 here。您可以在 GraphQL Introduction 了解更多关于 GraphQL 的信息,该文章发布在 React 博客上,或者您可以阅读 GraphQL specifications。这是一个持续进行中的项目。
此软件包与 Eloquent 模型或任何其他数据源兼容。
- 允许创建作为请求端点的 查询 和 突变
- 可以为每个查询/突变定义自定义 中间件
- 查询返回 类型,可以设置自定义的 隐私 设置。
- 查询的字段可以选择通过
SelectFields
类从数据库动态检索。
它提供了以下功能和改进,比 Folklore 的原始软件包有更多优势
- 按操作授权
- 按字段回调定义其可见性(例如,从未认证的用户中隐藏)
SelectFields
抽象在resolve()
中可用,允许进行高级预加载,从而解决 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',
。
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
并移除调用
使用方法
- 模式
- 创建查询
- 创建突变
- 向突变添加验证
- 文件上传
- 授权
- 隐私
- 查询变量
- 自定义字段
- 预加载关系
- 类型关系查询
- 分页
- 批处理
- 标量类型
- 枚举
- 联合体
- 接口
- 输入对象
- JSON列
- 字段弃用
- 默认字段解析器
- 从v1升级到v2
- 从Folklore迁移
- 性能考虑
- 包装类型
模式
定义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');
然后您需要定义一个返回此类型(或列表)的查询。您还可以指定在解析方法中可用的参数。
<?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 ], // ... ] ]
这就完成了。您应该能够通过向/graphql
(或您在配置中选择的任何内容)的URL发送请求来查询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
属性添加到错误对象中。要找到验证错误,您应该检查具有等于'validation'
的message
的错误,然后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; use Closure; use GraphQL\Type\Definition\ResolveInfo; class UsersQuery extends Query { public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = null, Closure $getSelectFields = null): bool { // true, if logged in return ! Auth::guest(); } // ... }
或者我们可以利用通过GraphQL查询传递的参数
use Auth; use Closure; use GraphQL\Type\Definition\ResolveInfo; class UsersQuery extends Query { public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = null, Closure $getSelectFields = null): 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}}¶ms={"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(); } }
您的用户类型可能如下所示。`profile` 和 `posts` 关系也必须在 `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'], ] ]; } }
到目前为止,我们已经有了预期的配置文件和帖子类型
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
)
- 将内部逻辑转换为序列化的GraphQL输出(
这也意味着可以在这些方法中添加验证逻辑,以确保提供/接收的值是例如真正的链接。
标量类型必须实现所有方法;您可以使用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') ] ]; } }
联合体
联合类型是一个简单的枚举其他对象类型的抽象类型。联合类型的值实际上是包含对象类型之一的一个值。
如果您需要在同一查询中返回不相关的类型,这很有用。例如,在实现多个不同实体的搜索时。
定义联合类型的示例
<?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 ] ]; } // ... }
字段弃用
有时您可能想要弃用字段,但仍需保持向后兼容,直到客户端完全停止使用该字段。您可以使用directive
弃用字段。如果您将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
- 从
- 如果您正在使用自定义 Scalar 类型
- parseLiteral 方法的签名已更改(由于 webonxy 库的升级)
- 从
public function parseLiteral($ast)
- 到
public function parseLiteral($valueNode, ?array $variables = null)
- 从
- parseLiteral 方法的签名已更改(由于 webonxy 库的升级)
- 方法字段签名已更改
- 如果您想使用
UploadType
,必须手动将其添加到您的模式中的types
中。::getInstance()
方法已消失,您可以通过GraphQL::type('Upload')
类似于其他类型来引用它。 - 遵循 Laravel 约定,使用复数形式命名空间(例如,新查询放置在
App\GraphQL\Queries
中,而不是App\GraphQL\Query
);相应的make
命令已调整。这不会破坏任何现有代码,但代码生成将使用新模式。 - 请务必阅读变更日志以获取更多详细信息
从Folklore迁移
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
(默认模式)已重命名为default_schema
middleware_schema
不存在了,它现在定义在schema.<name>.middleware
中
- 已移除的设置
- 更改命名空间引用
- 从
Folklore\
- 到
Rebing\
- 从
- 请参阅从 v1 升级到 v2 的升级指南以了解所有函数签名更改
- 特性
ShouldValidate
已不存在;提供的功能已集成到Field
- 查询/突变的解析方法的第一个参数现在是
null
(之前默认为空数组)
性能考虑
类型的延迟加载
类型的延迟加载是一种提高启动性能的方法。
如果您使用别名声明类型,则不支持。如果不是这种情况,您可以通过将 lazyload_types
设置为 true
来启用它。
不支持的延迟加载的别名示例
例如,您不能有一个具有 $name
属性 example
的查询类 ExampleQuery
,但使用不同的名称注册它;这将 不工作
'query' => [ 'aliasedEXample' => ExampleQuery::class, ],
包装类型
您可以使用包装类型向查询和突变添加更多信息。类似于分页是如何工作的,您可以对您想要注入的额外数据进行相同的操作(查看测试示例)。例如,在您的查询中
public function type(): Type { return GraphQL::wrapType( 'PostType', 'PostMessageType', \App\GraphQL\Types\WrapMessagesType::class, ); } public function resolve($root, $args) { return [ 'data' => Post::find($args['post_id']), 'messages' => new Collection([ new SimpleMessage("Congratulations, the post was found"), new SimpleMessage("This post cannot be edited", "warning"), ]), ]; }