glesys/butler-graphql

针对Laravel的具有观点的GraphQL包

v11.0.0 2024-05-29 06:31 UTC

README

.github/workflows/test.yml Code Coverage Scrutinizer Code Quality Packagist License CII Best Practices

Butler GraphQL

Butler GraphQL是一个具有观点的包,它使得使用Laravel快速且轻松地提供GraphQL API变得简单。

入门

  1. 安装glesys/butler-graphql包。
composer require glesys/butler-graphql

注意:如果您使用的是Laravel < 5.5或Lumen,您需要手动注册Butler\Graphql\ServiceProvider::class

  1. 创建一个GraphQL模式文件。默认位置是app/Http/Graphql/schema.graphql
type Query {
    pendingSignups: [Signup!]!
}

type Signup {
    email: String!
    verificationToken: String!
}
  1. pendingSignups查询创建一个解析器。
<?php

namespace App\Http\Graphql\Queries;

class PendingSignups
{
    public function __invoke($root, $args, $context)
    {
        return Signups::where('status', 'pending')->get();
    }
}
  1. 创建一个具有Butler\Graphql\Concerns\HandlesGraphqlRequests特质的主控制器。
<?php

namespace App\Http\Controllers;

use Butler\Graphql\Concerns\HandlesGraphqlRequests;

class GraphqlController extends Controller
{
    use HandlesGraphqlRequests;
}
  1. 为您的GraphQL API端点添加路由。
$router->post('/graphql', GraphqlController::class);
  1. 使用类似GraphiQLInsomnia的工具与您的GraphQL API交互。

深入了解

查询

查询解析器由位于App\Http\Graphql\Queries命名空间中的类表示。它们应该与查询同名,但使用StudlyCased,即pendingSignups => PendingSignups

查询作为可调用项触发,因此您只需实现__invoke方法即可。

所有解析方法都会传入以下参数

/**
 * @param  mixed  $root
 * @param  array  $args
 * @param  array  $context
 * @param  \GraphQL\Type\Definition\ResolveInfo  $info
 */

除了从解析方法返回可数组和对象之外,您还可以返回可调用项,这些可调用项将以相同的参数集调用。

public function __invoke()
{
    return function ($root, $args, $context, $info) {
        //
    };
}

突变

突变解析器由位于App\Http\Graphql\Mutations命名空间中的简单类表示。

从技术上讲,突变和查询是同一件事。它们都可以接受参数并返回带有字段的数据类型。将它们分开更多的是一种约定,而不是一种要求。

在REST中,任何请求都可能对服务器产生一些副作用,但按照约定,不建议使用GET请求来修改数据。GraphQL类似,从技术上讲,任何查询都可以实现为导致数据写入。然而,建立一个约定,即任何导致写入的操作应显式通过突变发送,是有用的。

https://graphql.net.cn/learn/queries/#mutations

类型

解析类型字段与查询和突变一样简单。在App\Http\Graphql\Types命名空间中定义一个简单的类,并为字段使用camelCased方法名称。

<?php

namespace App\Http\Graphql\Types;

class Signup
{
    public function verificationToken($source, $args, $context, $info)
    {
        return $source->token;
    }
}

注意:如果您在GraphQL模式定义中的字段名称与源对象的键(对于可数组)或属性(对于对象)匹配,则不需要为该字段定义解析器方法。

接口

Butler GraphQL支持在模式中使用接口,但需要一点帮助才能知道用于解析字段的数据类型。

告诉Butler GraphQL使用什么类型的最容易的方法是提供数据中的__typename键或属性。例如

<?php

namespace App\Http\Graphql\Types;

class Post
{
    public function attachment($source, $args, $context, $info)
    {
        return [
            '__typename' => 'Photo',
            'height' => 200,
            'width' => 300,
        ];
    }
}

您还可以在父解析器中使用resolveTypeFor[Field]来动态决定使用哪种类型

<?php

namespace App\Http\Graphql\Types;

class Post
{
    public function attachment($source, $args, $context, $info)
    {
        return [
            'height' => 200,
            'width' => 300,
        ];
    }

    public function resolveTypeForAttachment($source, $context, $info)
    {
        if (isset($source['height'], $source['width'])) {
            return 'Photo';
        }
        if (isset($source['length'])) {
            return 'Video';
        }
    }
}

对于查询和突变,您只需定义一个resolveType方法

<?php

namespace App\Http\Graphql\Queries;

use App\Attachment;

class Attachments
{
    public function __invoke($source, $args, $context, $info)
    {
        return Attachment::all();
    }

    public function resolveType($source, $context, $info)
    {
        return $source->type; // `Photo` or `Video`
    }
}

如果没有上述任何一种,且数据是对象,Butler GraphQL将回退到数据的基本类名。

N+1和数据加载器

Butler GraphQL包括一个简单的数据加载器,以防止在加载嵌套数据时出现n+1问题。它在$context['loader']中可用,并且非常易于使用

<?php

namespace App\Http\Graphql\Types;

use App\Models\Article;

class Article
{
    public function comments(Article $source, $args, $context, $info)
    {
        return $context['loader'](function ($articleIds) {
            return Comment::whereIn('article_id', $articleIds)
                ->cursor()
                ->groupBy('article_id');
        }, [])->load($source->id);
    }
}

注意: 您可以将默认值作为加载器工厂的第二个参数提供。当您知道某些文章可能没有评论时,这很有用,例如上面的示例中。默认值为 null

共享数据加载器

如果您有多个解析器与相同的基本数据一起工作,您不需要复制代码或处理数据库的额外往返。

您需要做的就是定义一个单独的加载器函数,并在解析器中重用它。

<?php

namespace App\Http\Graphql\Types;

use App\Models\Article;
use Closure;

class Article
{
    public function comments(Article $source, $args, $context, $info)
    {
        return $context['loader'](Closure::fromCallable([$this, 'loadComments']), [])
            ->load($source->id);
    }

    public function topVotedComment(Article $source, $args, $context, $info)
    {
        $comments = yield $context['loader'](Closure::fromCallable([$this, 'loadComments']))
            ->load($source->id);

        return collect($comments)->sortByDesc('votes')->first();
    }

    private function loadComments($articleIds)
    {
        return Comment::whereIn('article_id', $articleIds)
            ->cursor()
            ->groupBy('article_id');
    }
}

Butler GraphQL 将确保 loadComments 只调用一次。

如果您不想使用 Closure::fromCallable(...),您可以将 loadComments 的可访问性更改为 public

将大型模式文件拆分为多个文件

Butler GraphQL 允许您轻松地将 GraphQL 模式文件的某些部分拆分到单独的文件中。

schema.graphql

type Query {
    users: [User!]!
}

type User {
    id: ID!
    username: String!
}

schema-user-attributes.graphql

extend type User {
    firstName: String
    lastName: String
    email: String!
}

注意: 基础模式总是必需的,并且建议避免使用多级扩展。

自定义

实际上没有真正需要配置 Butler GraphQL。它考虑到 约定优于配置,应该可以在没有任何配置的情况下立即使用。

如果您想覆盖任何可用设置,可以使用以下环境变量之一发布配置文件。

php artisan vendor:publish

更改模式位置和命名空间

  • BUTLER_GRAPHQL_SCHEMA – 默认为 app_path('Http/Graphql/schema.graphql')
  • BUTLER_GRAPHQL_NAMESPACE – 默认为 'App\\Http\\Graphql\\'

更改部分模式文件的路径和模式

  • BUTLER_GRAPHQL_SCHEMA_EXTENSIONS_PATH – 默认为 app_path('Http/Graphql/')
  • BUTLER_GRAPHQL_SCHEMA_EXTENSIONS_GLOB – 默认为 'schema-*.graphql'

调试

  • BUTLER_GRAPHQL_INCLUDE_DEBUG_MESSAGE – 设置为 true 以在错误响应中包含实际的错误消息。默认为 false
  • BUTLER_GRAPHQL_INCLUDE_TRACE – 设置为 true 以在错误响应中包含堆栈跟踪。默认为 false

Debugbar

Butler GraphQL 支持在使用 laravel-debugbar 时自动装饰响应,以包含额外的调试信息。如果已安装 barryvdh/laravel-debugbar,则数据库查询和内存使用等详细信息将自动在响应中可用。

要安装和激活它,只需将 barryvdh/laravel-debugbar 作为 require-dev 依赖项安装即可。

composer require barryvdh/laravel-debugbar --dev

安装后,请确保 APP_DEBUG 设置为 true,就这么简单。

通过将默认配置文件 default config file 复制到 config/debugbar.php 并根据需要进行调整,可以轻松地自定义要收集并包含在响应中的数据。

授权

如果您需要授权 GraphQL 请求,您可以使用 beforeExecutionHook 方法来检查模式和查询,并在那里实现您的授权逻辑。

<?php

namespace App\Http\Controllers;

use Butler\Graphql\Concerns\HandlesGraphqlRequests;
use GraphQL\Type\Schema;
use GraphQL\Language\AST\DocumentNode;

class GraphqlController extends Controller
{
    use HandlesGraphqlRequests;

    /**
     * @param  $schema         A parsed version of your GraphQL schema
     * @param  $query          A parsed version of the consumer's query
     * @param  $operationName  The passed operation name (for when the query contains multiple operations)
     * @param  $variables      Any variables passed alongside the query
     */
    public function beforeExecutionHook(Schema $schema, DocumentNode $query, string $operationName = null, $variables = null): void
    {
        // TODO: Implement custom authorization logic here
    }
}

如何贡献

开发发生在 GitHub 上;任何使用拉取请求的典型工作流程都受到欢迎。同样,我们使用 GitHub 问题跟踪器来处理所有报告(无论报告的性质如何,功能请求、错误等)。

所有更改都应覆盖单元测试,如果测试不可能或非常不实际,那么在拉取请求的评论部分进行讨论是有道理的。

代码标准

由于库旨在用于 Laravel 应用程序,我们鼓励遵循 上游 Laravel 实践 的代码标准 - 简而言之,这意味着 PSR-2PSR-4