PHP `GraphQL` `Lighthouse` (Laravel):构建 `GraphQL` 服务器端

好的,各位观众老爷们,今天咱们不聊风花雪月,就来聊聊怎么用PHP、GraphQL和Lighthouse(Laravel)这三剑客,打造一个坚如磐石的GraphQL服务器端。准备好了吗?系好安全带,咱们要起飞啦!

开场白:GraphQL,不再让你迷路的数据高速公路

在RESTful API的世界里,前端小伙们经常抱怨:“我要A数据,你给我A、B、C,我要C数据,你又给我C、D、E!你这是在浪费我的流量啊!”。GraphQL就是来拯救他们的,它允许客户端精确地请求它需要的数据,不多不少,就像自助餐一样,想吃啥拿啥。

第一部分:GraphQL 基础概念速览

在深入代码之前,咱们先来扫盲一下GraphQL的基础概念。不用怕,都是些很容易理解的玩意儿。

概念 解释 举例
Schema GraphQL世界的蓝图,定义了你可以查询什么数据,以及这些数据的结构。 就像数据库表结构,告诉你有哪些表,表里有哪些字段。
Query 客户端用来请求数据的请求。 query { user(id: 123) { name email } } (请求id为123的用户的名字和邮箱)
Mutation 客户端用来修改数据的请求。 mutation { createUser(name: "张三", email: "[email protected]") { id name email } } (创建一个名为张三,邮箱为[email protected]的用户,并返回其id、name和email)
Resolver 一个函数,负责从数据源(例如数据库)获取数据,并将其格式化成GraphQL Schema中定义的结构。 就像Controller里的方法,负责从数据库查询数据,然后返回给前端。
Type 定义了GraphQL Schema中数据的类型,例如String, Int, Boolean, 还有自定义的Object Type。 例如 type User { id: ID! name: String! email: String } (定义了一个User类型,包含id, name, email字段,类型分别是ID, String, String,!表示非空)

第二部分:Lighthouse闪亮登场

Lighthouse是啥?简单来说,它是一个为Laravel量身定制的GraphQL包,它让构建GraphQL API变得像搭积木一样简单。它提供了很多方便的功能,比如:

  • Schema定义语言(SDL)支持: 用一种简洁易懂的语言定义GraphQL Schema。
  • 自动Resolver生成: 根据Schema自动生成Resolver函数,省时省力。
  • 强大的指令系统: 通过指令,可以轻松实现认证、授权、分页等功能。

第三部分:实战演练:构建一个简单的用户管理GraphQL API

咱们来撸起袖子,用Lighthouse构建一个简单的用户管理GraphQL API。

1. 安装Lighthouse

首先,确保你已经安装了Laravel。然后,通过Composer安装Lighthouse:

composer require nuwave/lighthouse

安装完成后,运行Lighthouse的安装命令:

php artisan lighthouse:install

这个命令会在你的Laravel项目中生成一些配置文件和目录,包括graphql目录,里面存放你的GraphQL Schema文件。

2. 定义GraphQL Schema

graphql/schema.graphql文件中,定义我们的用户相关的Schema。

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

type Post {
    id: ID!
    title: String!
    content: String!
    user: User! @belongsTo
}

type Query {
  users: [User!]! @all
  user(id: ID! @eq): User @find
  posts: [Post!]! @all
  post(id: ID! @eq): Post @find
}

type Mutation {
  createUser(name: String!, email: String!): User! @create
  updateUser(id: ID!, name: String, email: String): User! @update
  deleteUser(id: ID!): User @delete
  createPost(title: String!, content: String!, user_id: ID!): Post! @create
  updatePost(id: ID!, title: String, content: String, user_id: ID): Post! @update
  deletePost(id: ID!): Post @delete
}

解释一下:

  • type Usertype Post:定义了User和Post的类型,以及它们的字段。 ! 表示该字段不能为空。
  • @hasMany@belongsTo: 定义了User和Post之间的关系,一个User可以拥有多个Post,一个Post属于一个User。
  • type Query:定义了可以查询的数据。 @all 指令表示查询所有数据,@find 指令表示根据id查询数据,@eq指令表示参数等于指定值。
  • type Mutation:定义了可以修改的数据。 @create 指令表示创建数据,@update 指令表示更新数据,@delete 指令表示删除数据。

3. 创建Eloquent模型

如果还没创建,我们需要创建User和Post模型。

php artisan make:model User
php artisan make:model Post

然后在User模型中添加关系:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateFoundationAuthUser as Authenticatable;
use IlluminateNotificationsNotifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

在Post模型中添加关系:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'content',
        'user_id',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

4. 运行数据库迁移

创建数据库迁移,并运行它们。

php artisan make:migration create_users_table
php artisan make:migration create_posts_table

在迁移文件中定义表结构,这里省略具体代码,确保users表包含id, name, email, password字段,posts表包含id, title, content, user_id字段。然后运行迁移:

php artisan migrate

5. 尝试GraphQL查询

现在,你可以通过GraphQL客户端(例如GraphiQL,Postman的GraphQL插件)发送GraphQL查询了。访问/graphql路由(Lighthouse默认的GraphQL endpoint),你将会看到一个GraphiQL界面。

尝试以下查询:

query {
  users {
    id
    name
    email
    posts {
      id
      title
    }
  }
}

这个查询会返回所有用户的信息,包括他们的id, name, email,以及他们发表的所有文章的id和title。

尝试以下mutation:

mutation {
  createUser(name: "王五", email: "[email protected]") {
    id
    name
    email
  }
}

这个mutation会创建一个名为王五,邮箱为[email protected]的用户,并返回其id, name和email。

第四部分:自定义Resolver

Lighthouse的指令已经很强大了,但有时候我们需要更灵活地控制数据的获取和处理。这时候,就需要自定义Resolver了。

例如,我们想要创建一个查询,返回所有邮箱包含特定关键词的用户。

1. 修改GraphQL Schema

graphql/schema.graphql文件中添加一个新的Query:

type Query {
  usersByEmail(email: String!): [User!]! @resolver(class: "App\GraphQL\Queries\UserQuery", method: "byEmail")
}

这里,我们定义了一个名为usersByEmail的Query,它接收一个email参数,类型为String。 @resolver 指令告诉Lighthouse,这个Query的Resolver函数位于AppGraphQLQueriesUserQuery类的byEmail方法中。

2. 创建Resolver类

创建一个新的类 AppGraphQLQueriesUserQuery,并添加byEmail方法:

<?php

namespace AppGraphQLQueries;

use AppModelsUser;

class UserQuery
{
    /**
     * @param  null  $_
     * @param  array<string, mixed>  $args
     */
    public function byEmail($_, array $args)
    {
        $email = $args['email'];
        return User::where('email', 'like', "%{$email}%")->get();
    }
}

这个方法接收两个参数:

  • $_:父对象,在嵌套查询中会用到,这里我们不需要。
  • $args:客户端传递的参数,这里我们接收email参数。

这个方法使用Eloquent ORM查询数据库,返回所有邮箱包含指定关键词的用户。

3. 尝试新的GraphQL查询

现在,你可以通过GraphQL客户端发送新的GraphQL查询了:

query {
  usersByEmail(email: "example") {
    id
    name
    email
  }
}

这个查询会返回所有邮箱包含 "example" 关键词的用户的信息。

第五部分:GraphQL的认证与授权

光有数据还不够,咱们还得保护好数据,防止坏人搞破坏。认证和授权就是两把利剑。

1. 认证 (Authentication)

Lighthouse 提供了 @guard 指令来进行认证。 首先,确保你的 config/auth.php 文件中配置了合适的 guards。 默认情况下,Laravel 会配置一个 web guard (用于浏览器 session) 和一个 api guard (用于 API 请求,通常使用 tokens)。

假设我们使用 api guard, 并且使用 Laravel Passport 来生成 API tokens。

修改你的 GraphQL schema,在需要认证的 Query 或 Mutation 上添加 @guard 指令:

type Query {
  me: User! @auth @can(ability: "view", model: "App\Models\User")
}

这里,@auth 指令会检查用户是否已经认证。 如果用户未认证,则会抛出一个错误。@can 指令会检查用户是否有权限查看当前用户的信息。ability参数指定了权限,model参数指定了模型。

2. 授权 (Authorization)

使用 @can 指令来进行授权。 为了使用 @can 指令,你需要定义 Laravel Policies。

首先,创建一个 Policy:

php artisan make:policy UserPolicy --model=User

然后,在 app/Policies/UserPolicy.php 文件中定义你的授权逻辑:

<?php

namespace AppPolicies;

use AppModelsUser;
use IlluminateAuthAccessHandlesAuthorization;

class UserPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view the model.
     *
     * @param  AppModelsUser  $user
     * @param  AppModelsUser  $model
     * @return mixed
     */
    public function view(User $user, User $model)
    {
        return $user->id === $model->id; // 只有用户可以查看自己的信息
    }

    /**
     * Determine whether the user can update the model.
     *
     * @param  AppModelsUser  $user
     * @param  AppModelsUser  $model
     * @return mixed
     */
    public function update(User $user, User $model)
    {
        return $user->id === $model->id; // 只有用户可以更新自己的信息
    }

    /**
     * Determine whether the user can delete the model.
     *
     * @param  AppModelsUser  $user
     * @param  AppModelsUser  $model
     * @return mixed
     */
    public function delete(User $user, User $model)
    {
        return $user->id === $model->id; // 只有用户可以删除自己的信息
    }
}

最后,在 app/Providers/AuthServiceProvider.php 文件中注册你的 Policy:

<?php

namespace AppProviders;

use AppModelsUser;
use AppPoliciesUserPolicy;
use IlluminateFoundationSupportProvidersAuthServiceProvider as ServiceProvider;
use IlluminateSupportFacadesGate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        User::class => UserPolicy::class,
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        //
    }
}

现在,只有经过认证,并且拥有相应权限的用户才能访问GraphQL API。

第六部分:Lighthouse的实用技巧

  • 分页: 使用 @paginate 指令可以轻松实现分页功能。
  • 数据校验: 使用 @rules 指令可以对输入数据进行校验。
  • 自定义指令: 如果Lighthouse自带的指令不够用,你可以自定义指令。
  • 批量查询: 使用 @field 指令可以将多个查询合并成一个。
  • 缓存: 使用 @cache 指令可以缓存查询结果,提高性能。

第七部分:常见问题及解决方案

  • N+1问题: 这是GraphQL常见的问题,可以使用Lighthouse的 @with 指令预加载关联数据,避免N+1问题。
  • 性能问题: 可以通过缓存、代码优化、数据库优化等方式提高性能。
  • 安全问题: 要注意防止SQL注入、跨站脚本攻击等安全问题。
  • 版本控制: GraphQL Schema也需要版本控制,可以使用Git等工具进行管理。

第八部分:总结

好了,各位,今天的GraphQL之旅就到这里了。我们一起学习了GraphQL的基础概念,Lighthouse的使用方法,以及如何构建一个简单的用户管理GraphQL API。 希望今天的分享能帮助大家更好地理解和应用GraphQL技术。记住,实践是检验真理的唯一标准,多敲代码,多思考,你也能成为GraphQL高手!

最后的温馨提示:

  • 遇到问题不要慌,Google一下,Stack Overflow一下,总能找到答案。
  • 多看官方文档,那是最好的学习资料。
  • 加入GraphQL社区,和大家一起交流学习。

下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注