Laravel API 系列教程(五):基于 GraphQL 构建 Laravel API —— 高级使用篇
上一篇教程中我们重点探讨了 GraphQL 的概念及其在 Laravel API 构建中的基本使用,本篇教程将在其基础上探讨关于 GraphQL 的一些更高级的使用。
带参数的查询
带参数的查询是构建 API 时的常见操作,上一篇教程中编写的列表查询功能已经支持带参数查询,只需传入相应查询参数即可:
嵌套资源
关联关系是 Laravel 模型操作中又一重要功能,我们可以通过关联关系获取嵌套资源。在 User
模型中定义用户与评论的关联关系如下:
public function comments()
{
return $this->hasMany(Comment::class, 'user_id', 'id');
}
然后创建 CommentType
类型:
php artisan make:graphql:type CommentType
编辑新生成的 CommentType
类:
namespace App\GraphQL\Type;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Type as BaseType;
use GraphQL;
class CommentType extends BaseType
{
protected $attributes = [
'name' => 'Comment',
'description' => 'A Comment'
];
public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the comment'
],
'content' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The content of the comment'
]
];
}
}
接下来在配置文件 config/graphql.php
中注册这个类型:
'types' => [
...
'Comment' => \App\GraphQL\Type\CommentType::class,
],
最后在 UserType
中新增 comments
字段及对应解析方法:
public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of the user'
],
'comments' => [
'type' => Type::listOf(GraphQL::type('Comment')),
'description' => 'The comments by the user'
]
];
}
...
protected function resolveCommentsField($root, $args)
{
if (isset($args['id'])) {
return $root->comments->where('id', $args['id']);
}
return $root->comments;
}
这样,我们就可以通过 GraphiQL 演示嵌套资源的获取了:
枚举
这里的枚举和我们在编程语言中的枚举概念一样,要使用枚举,可以通过以下 Artisan 命令创建一个 CommentStatusEnum
类:
php artisan make:graphql:enum CommentStatusEnum
编辑新生成的 CommentStatusEnum
类:
namespace App\GraphQL\Enums;
use Folklore\GraphQL\Support\EnumType;
class CommentStatusEnum extends EnumType
{
protected $enumObject = true;
protected $attributes = [
'name' => 'CommentStatus',
'description' => 'Comment status enum'
];
public function values() {
return [
'APPROVED' => '1',
'REJECT' => '0'
];
}
}
然后在配置文件 config/graphql.php
中注册这个枚举类型:
'types' => [
...
'CommentStatusEnum' => \App\GraphQL\Enums\CommentStatusEnum::class
],
最后在 CommentType
中新增 status
字段:
public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the comment'
],
'content' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The content of the comment'
],
'status' => [
'type' => GraphQL::type('CommentStatusEnum'),
'description' => 'The status of the comment'
]
];
}
这样就可以在 GraphiQL 中演示枚举功能的使用了:
接口
GraphQL 中的接口和面向对象编程语言中的接口(Laravel中叫契约)作用类似,只不过是用来约束一组相关类型必须实现的方法。我们可以通过 Artisan 命令生成接口:
php artisan make:graphql:interface CharacterInterface
编辑刚刚生成的 CharacterInterface
接口代码:
// app/GraphQL/Interfaces/CharacterInterface.php
namespace App\GraphQL\Interfaces;
use GraphQL;
use Folklore\GraphQL\Support\InterfaceType;
use GraphQL\Type\Definition\Type;
class CharacterInterface extends InterfaceType {
protected $attributes = [
'name' => 'Character',
'description' => 'Character interface.',
];
public function fields() {
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the character.'
],
'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');
} else if ($type === 'droid') {
return GraphQL::type('Droid');
}
}
}
然后我们来定义一个实现该接口的子类型 HumanType
(自行通过 Artisan 命令生成),需要注意的是这里对接口的实现并不是通过 implement
关键字,而是通过 interfaces()
方法:
// app/GraphQL/Types/HumanType.php
namespace App\GraphQL\Types;
use GraphQL;
use Folklore\GraphQL\Support\Type as GraphQLType;
use GraphQL\Type\Definition\Type;
class HumanType extends GraphQLType {
protected $attributes = [
'name' => 'Human',
'description' => 'A human.'
];
public function fields() {
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the human.',
],
'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() {
return [
GraphQL::type('Character')
];
}
}
自定义字段
除了 GraphQL 自带的常用字段外,对于一些复杂类型的字段,我们还可以基于 GraphQL 提供的接口进行扩展来实现。首先,创建一个自定义字段类:
php artisan make:graphql:field PictureField
然后编辑刚生成的 PictureField
类:
// app/GraphQL/Fields/PictureField
namespace App\GraphQL\Fields;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Field;
class PictureField extends Field {
protected $attributes = [
'description' => 'A picture'
];
public function type(){
return Type::string();
}
public function args()
{
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;
}
}
最后我们在 UserType
中新增一个 picture
字段来使用这个自定义字段:
// app/GraphQL/Type/UserType
namespace App\GraphQL\Type;
use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Type as GraphQLType;
use App\GraphQL\Fields\PictureField;
class UserType extends GraphQLType {
protected $attributes = [
'name' => 'User',
'description' => 'A user'
];
public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the user'
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user'
],
'picture' => PictureField::class
];
}
}
渴求式加载
为提升查询效率,还可以在关联查询中使用渴求式加载。编辑 UserQuery
的 resolve
方法:
public function resolve($root, $args, $context, ResolveInfo $info)
{
$fields = $info->getFieldSelection($depth = 3);
if (isset($args['id'])) {
$users = User::where('id', $args['id']);
} elseif (isset($args['email'])) {
$users = User::where('email', $args['email']);
} else {
$users = User::query();
}
foreach ($fields as $field => $keys) {
if ($field === 'comments') {
$users->with('comments');
}
}
return $users->get();
}
然后在 GraphiQL 演示该功能:
基于 jwt-auth 实现接口认证
在 Laravel API 系列二教程中我们已经演示了如何基于 jwt-auth 实现接口认证,这里我们将在那篇教程基础上进行编码。API 首次认证成功后,我们通过在每次请求时传递 Authorization: Bearer {yourtokenhere}
请求头的方式实现后续认证。
以 UpdateUserPasswordMutation
为例,要让该操作认证后才能操作,可以在该方法中重写父类的 authenticate
方法:
public function authenticated($root, $args, $context)
{
return JWTAuth::parseToken()->authenticate() ? true : false;
}
这样,再次通过 GraphiQL 访问时就会报错:
而认证通过后(参考系列二教程实现)即可更新成功(Token 值通过在浏览器登录成功后在请求头中获取):
授权操作也是类似,我们需要覆盖父类的 authorize
来实现自定义的授权逻辑,感兴趣的同学可以试下。更多关于 GraphQL 在 Laravel API 中的使用请参考本系列教程使用的 Github 项目 folklore/graphql
以及 GraphQL 官方文档。
下一篇我们将讨论如何在 Vue 前端应用中访问基于 GraphQL 构建的 Laravel API 接口。
8 Comments
文件上传GraphQL 怎么实现
分页见:http://graphql.cn/learn/pagination/
最近在尝试通过 rebing/graphql-laravel 来封装后面的服务,但这货貌似更适合封装数据库ORM操作。
看了下只有第一层数据可以使用参数过滤,深层次的数据如何添加过滤参数呢?
https://github.com/rebing/graphql-laravel/issues/147