Laravel 13 GraphQL 集成完全指南

GraphQL 是一种用于 API 的查询语言,提供了比 REST 更灵活的数据获取方式。本文将深入探讨如何在 Laravel 13 中集成和使用 GraphQL。

GraphQL 基础

为什么选择 GraphQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// REST API - 多次请求获取不同数据
GET /api/users/1
GET /api/users/1/posts
GET /api/users/1/comments

// GraphQL - 单次请求获取所有数据
query {
user(id: 1) {
name
email
posts {
title
comments {
content
}
}
}
}

安装 Lighthouse

安装依赖

1
2
3
composer require nuwave/lighthouse
php artisan vendor:publish --tag=lighthouse-schema
php artisan vendor:publish --tag=lighthouse-config

安装 GraphQL Playground

1
composer require mll-lab/laravel-graphql-playground

配置路由

1
2
3
4
5
6
7
8
9
10
// config/lighthouse.php
return [
'route' => [
'prefix' => 'graphql',
'middleware' => [
\Nuwave\Lighthouse\Support\Http\Middleware\AcceptJson::class,
\App\Http\Middleware\Authenticate::class,
],
],
];

Schema 定义

基础 Schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# graphql/schema.graphql
type Query {
users: [User!]! @all
user(id: ID @eq): User @find
posts: [Post!]! @all
post(id: ID @eq): Post @find
}

type User {
id: ID!
name: String!
email: String!
posts: [Post!]! @hasMany
created_at: DateTime!
updated_at: DateTime!
}

type Post {
id: ID!
title: String!
content: String!
author: User! @belongsTo
comments: [Comment!]! @hasMany
created_at: DateTime!
}

type Comment {
id: ID!
content: String!
user: User! @belongsTo
post: Post! @belongsTo
created_at: DateTime!
}

分页查询

1
2
3
4
5
6
7
8
9
10
11
type Query {
users(
search: String @where(operator: "like")
orderBy: _ @orderBy(columns: ["name", "created_at"])
): [User!]! @paginate(defaultCount: 15)

posts(
published: Boolean @eq
category_id: ID @eq
): [Post!]! @paginate(defaultCount: 10)
}

类型定义

标量类型

1
2
3
4
scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")
scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date")
scalar JSON @scalar(class: "MLL\\GraphQLScalars\\JSON")
scalar Email @scalar(class: "MLL\\GraphQLScalars\\Email")

枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum UserRole {
ADMIN @enum(value: "admin")
EDITOR @enum(value: "editor")
USER @enum(value: "user")
}

enum PostStatus {
DRAFT @enum(value: "draft")
PUBLISHED @enum(value: "published")
ARCHIVED @enum(value: "archived")
}

type User {
id: ID!
name: String!
role: UserRole!
}

type Post {
id: ID!
title: String!
status: PostStatus!
}

接口类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Node {
id: ID!
created_at: DateTime!
}

type User implements Node {
id: ID!
name: String!
email: String!
created_at: DateTime!
}

type Post implements Node {
id: ID!
title: String!
content: String!
created_at: DateTime!
}

type Query {
node(id: ID!): Node @node
}

联合类型

1
2
3
4
5
union SearchResult = User | Post

type Query {
search(query: String!): [SearchResult!]!
}

查询操作

基础查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
query {
users {
id
name
email
}
}

query {
user(id: 1) {
name
posts {
title
content
}
}
}

带参数查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
query GetUsers($search: String, $orderBy: [OrderByClause!]) {
users(search: $search, orderBy: $orderBy) {
id
name
email
}
}

# Variables
{
"search": "%john%",
"orderBy": [
{"field": "name", "order": "ASC"}
]
}

分页查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
query {
users(first: 10, page: 1) {
data {
id
name
email
}
paginatorInfo {
currentPage
lastPage
total
perPage
}
}
}

嵌套查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
query {
user(id: 1) {
name
posts(first: 5) {
data {
title
comments(first: 3) {
data {
content
user {
name
}
}
}
}
}
}
}

变更操作

定义 Mutation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type Mutation {
createUser(input: CreateUserInput! @spread): User! @create
updateUser(id: ID!, input: UpdateUserInput! @spread): User @update
deleteUser(id: ID!): User @delete

createPost(input: CreatePostInput! @spread): Post! @create
updatePost(id: ID!, input: UpdatePostInput! @spread): Post @update
deletePost(id: ID!): Post @delete
}

input CreateUserInput {
name: String!
email: String!
password: String! @hash
}

input UpdateUserInput {
name: String
email: String
password: String @hash
}

input CreatePostInput {
title: String!
content: String!
user_id: ID!
status: PostStatus
}

执行 Mutation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}

# Variables
{
"input": {
"name": "John Doe",
"email": "john@example.com",
"password": "secret123"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
id
name
email
}
}

# Variables
{
"id": "1",
"input": {
"name": "Jane Doe"
}
}

自定义解析器

创建解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

namespace App\GraphQL\Queries;

use App\Models\User;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

class UserQuery
{
public function search($root, array $args, GraphQLContext $context)
{
return User::query()
->where('name', 'like', "%{$args['query']}%")
->orWhere('email', 'like', "%{$args['query']}%")
->get();
}

public function profile($root, array $args, GraphQLContext $context)
{
return $context->user();
}
}

注册解析器

1
2
3
4
5
6
7
type Query {
search(query: String!): [User!]!
@field(resolver: "App\\GraphQL\\Queries\\UserQuery@search")

profile: User!
@field(resolver: "App\\GraphQL\\Queries\\UserQuery@profile")
}

Mutation 解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

namespace App\GraphQL\Mutations;

use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

class UserMutation
{
public function register($root, array $args, GraphQLContext $context)
{
$user = User::create([
'name' => $args['name'],
'email' => $args['email'],
'password' => Hash::make($args['password']),
]);

$token = $user->createToken('auth-token')->plainTextToken;

return [
'user' => $user,
'token' => $token,
];
}

public function updateProfile($root, array $args, GraphQLContext $context)
{
$user = $context->user();
$user->update($args['input']);

return $user;
}
}

认证与授权

认证中间件

1
2
3
4
5
6
7
8
9
10
type Query {
me: User @auth
profile: User @guard
}

type Mutation {
updateProfile(input: UpdateProfileInput!): User!
@guard
@field(resolver: "App\\GraphQL\\Mutations\\UserMutation@updateProfile")
}

授权指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Query {
users: [User!]! @all @can(ability: "viewAny", model: "App\\Models\\User")
user(id: ID!): User @find @can(ability: "view", find: "id")
}

type Mutation {
updateUser(id: ID!, input: UpdateUserInput!): User
@update
@can(ability: "update", find: "id")

deleteUser(id: ID!): User
@delete
@can(ability: "delete", find: "id")
}

自定义授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

namespace App\GraphQL\Directives;

use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;

class CanAccessDirective extends BaseDirective implements FieldMiddleware
{
public static function definition(): string
{
return /** @lang GraphQL */ <<<'GRAPHQL'
directive @canAccess(
role: String!
) on FIELD_DEFINITION
GRAPHQL;
}

public function handleField($field, $fieldArgs, $context, ResolveInfo $resolveInfo, Closure $next)
{
$user = $context->user();
$requiredRole = $this->directiveArgValue('role');

if (!$user || $user->role !== $requiredRole) {
throw new \Exception('Unauthorized access');
}

return $next($field, $fieldArgs, $context, $resolveInfo);
}
}

复杂度控制

配置复杂度

1
2
3
4
5
6
7
// config/lighthouse.php
return [
'security' => [
'max_query_complexity' => 1000,
'max_query_depth' => 10,
],
];

指令控制

1
2
3
4
5
6
7
8
type Query {
users: [User!]! @all @complexity(value: 100)
posts: [Post!]! @all @complexity(value: 50)
}

type User {
posts: [Post!]! @hasMany @complexity(value: 10)
}

订阅功能

定义订阅

1
2
3
4
5
type Subscription {
postCreated: Post!
commentAdded(postId: ID!): Comment!
userUpdated(id: ID!): User!
}

实现订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace App\GraphQL\Subscriptions;

use App\Models\Post;
use Nuwave\Lighthouse\Subscriptions\Subscriber;
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;

class PostCreated extends GraphQLSubscription
{
public function authorize(Subscriber $subscriber, $root): bool
{
return true;
}

public function filter(Subscriber $subscriber, $root): bool
{
return true;
}

public function resolve($root, array $args, $context)
{
return $root;
}
}

触发订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
use Nuwave\Lighthouse\Subscriptions\Facades\Subscription;

class PostController extends Controller
{
public function store(Request $request)
{
$post = Post::create($request->validated());

Subscription::broadcast('postCreated', $post);

return $post;
}
}

测试 GraphQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?php

namespace Tests\Feature\GraphQL;

use Tests\TestCase;
use App\Models\User;

class UserGraphQLTest extends TestCase
{
public function test_query_users()
{
User::factory()->count(3)->create();

$response = $this->graphQL(/** @lang GraphQL */ '
query {
users {
id
name
email
}
}
');

$response->assertJsonStructure([
'data' => [
'users' => [
'*' => ['id', 'name', 'email']
]
]
]);
}

public function test_mutation_create_user()
{
$response = $this->graphQL(/** @lang GraphQL */ '
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
', [
'input' => [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'secret123',
]
]);

$response->assertJsonStructure([
'data' => [
'createUser' => ['id', 'name', 'email']
]
]);
}

public function test_authenticated_query()
{
$user = User::factory()->create();

$response = $this->actingAs($user)->graphQL(/** @lang GraphQL */ '
query {
me {
id
name
}
}
');

$response->assertJson([
'data' => [
'me' => [
'id' => (string) $user->id,
'name' => $user->name,
]
]
]);
}
}

总结

Laravel 13 的 GraphQL 集成提供了:

  • 灵活的 Schema 定义
  • 强大的查询和变更操作
  • 完整的认证授权支持
  • 实时订阅功能
  • 复杂度控制
  • 完善的测试支持

GraphQL 为 API 开发提供了更灵活的选择,特别适合复杂的数据查询场景。