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
        ];
    }

}

渴求式加载

为提升查询效率,还可以在关联查询中使用渴求式加载。编辑 UserQueryresolve 方法:

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 接口。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: Laravel API 系列教程(四):基于 GraphQL 构建 Laravel API —— 基本使用篇

>> 下一篇: 解决前后端分离应用跨域请求利器 —— Laravel CORS 扩展包