lastdragon-ru/lara-asp-graphql

为 Laravel 提供的强大包集合 - GraphQL 扩展。

6.4.2 2024-09-20 13:09 UTC

README

本包提供高度强大的 @searchBy@sortBy@stream 指令,用于 lighthouse-php。该 @searchBy 指令提供基本的条件如 =>< 等、关系、not (<condition>)、枚举,以及自定义运算符支持。所有都是严格类型,因此您不再需要使用 Mixed 类型。该 @sortBy 不仅支持标准按列排序,还允许使用关系。😎

要求

安装

composer require lastdragon-ru/lara-asp-graphql

配置

配置可以用于自定义每种类型支持的运算符。在此之前,您需要通过以下命令发布它,然后您可以编辑 config/lara-asp-graphql.php

php artisan vendor:publish --provider=LastDragon_ru\\LaraASP\\GraphQL\\Provider --tag=config

指令

@searchBy

提供搜索(where 条件)的指令可能是最强大的。

阅读更多.

@sortBy

提供排序(order by 条件)的指令可能是最强大的。

阅读更多.

@stream 🧪

@paginate(和类似)指令不同,@stream 提供了一种统一的方式来执行 Eloquent/Query/Scout 构建器的偏移量/限制和游标分页。通过 @searchBy@sortBy 指令默认启用过滤和排序。

阅读更多.

@type

将标量转换为 GraphQL 类型。类似于 Lighthouse 的 @scalar 指令,但使用 Laravel 容器来解析实例,并支持 PHP 枚举。

阅读更多.

标量

重要

在使用之前,您应该注册标量,可以通过 AstManipulator(在 AST 操作时很有用)、TypeRegistry 或作为 Schema 中的自定义标量来完成。

scalar JsonString
@type(
    class: "LastDragon_ru\\LaraASP\\GraphQL\\Scalars\\JsonStringType"
)

JsonString

表示 JSON 字符串。

阅读更多.

Scout

Scout 也受到支持 🤩。您只需将 @search 指令添加到参数中。请注意,可用的运算符取决于 Scout 本身

请注意,如果添加了 @search 指令,生成的查询将仅期望 Scout 构建器。因此,建议使用非空 String! 类型以避免使用 Eloquent 构建器(如果搜索参数缺失或为 null,则将发生这种情况;请参阅 lighthouse#2465)。

输入类型自动生成

@searchBy/@sortBy 等构建器指令一起使用的类型可能是显式类型(当指定 input 名称 field(where: InputTypeName @searchBy): [Object!]! 时)或隐式类型(当使用 _ 时,field(where: _ @searchBy): [Object!]!)。它们的处理方式略有不同。

对于显式类型,除了联合和标记为忽略的字段外,所有字段都将被包括。

对于隐式类型,应用以下规则(按此顺序;具体指令可能有所不同,请查看其文档)

  • 联合?- 排除
  • 具有具体指令的 Operator 吗?- 包括
  • 具有 Nuwave\Lighthouse\Support\Contracts\FieldResolver 吗?
      • Nuwave\Lighthouse\Schema\Directives\RelationDirective 吗?- 如果是 ObjectObject 列表,则包括
      • Nuwave\Lighthouse\Schema\Directives\RenameDirective 吗?- 如果允许,是 scalar/enum(不是 Object),并且没有参数,则包括
      • 否则 - 排除
      • Object 或具有参数 - 排除
      • 否则 - 包括
  • 忽略(如果支持)- 排除

在转换字段时,一些原始指令将被复制到新生成的字段中。对于显式类型,除其他指令的操作符外的所有指令都将被复制。对于隐式类型,可以使用 builder.allowed_directives 设置来控制。请注意指令的位置 - 包不执行任何检查以确保复制的指令在 INPUT_FIELD_DEFINITION 上允许,它只是按原样复制。

构建器字段/列名称

默认情况下,@searchBy/@sortBy 将嵌套/相关字段转换为点字符串:例如,{user: {name: asc}} 将转换为 user.name。您可以通过 BuilderFieldResolver 重新定义此行为

// AppProvider

$this->app->bind(
    LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\BuilderFieldResolver::class,
    MyBuilderFieldResolver::class,
);

构建器类型检测

@searchBy/@sortBy 等指令为每个构建器类型(Eloquent/Scout等)提供唯一的一组操作符和其他功能。对于标准 Lighthouse 指令(如 @all@paginated@search 等)的当前构建器检测工作良好,并依赖于关系/查询/解析器正确的类型提示。如果丢失类型提示或使用了联合类型,则可能会得到 BuilderUnknown 错误。

<?php declare(strict_types = 1);

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Comment extends Model {
    protected $table = 'comments';

    /**
     * Will NOT work
     */
    public function user() {
        return $this->belongsTo(User::class);
    }

    /**
     * Must be
     */
    public function user(): BelongsTo {
        return $this->belongsTo(User::class);
    }
}

如果您实现了自定义指令,这些指令在内部增强构建器(就像标准指令一样),则可能会得到 BuilderUnknown 错误,因为未检测到正确/预期的构建器类型。在这种情况下,您的指令应实现 BuilderInfoProvider 接口,并明确指定构建器类型。

<?php declare(strict_types = 1);

namespace App\GraphQL\Directives;

use Illuminate\Database\Eloquent\Builder;
use LastDragon_ru\LaraASP\GraphQL\Builder\BuilderInfo;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\BuilderInfoProvider;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource;
use Nuwave\Lighthouse\Support\Contracts\Directive;
use Override;

class CustomDirective implements Directive, BuilderInfoProvider {
    #[Override]
    public static function definition(): string {
        return 'directive @custom';
    }

    #[Override]
    public function getBuilderInfo(TypeSource $source): ?BuilderInfo {
        return BuilderInfo::create(Builder::class);
    }

    public function __invoke(): mixed {
        return null;
    }
}

打印机

该包提供了对 Printer 的绑定,因此您可以简单地使用

<?php declare(strict_types = 1);

use LastDragon_ru\LaraASP\Dev\App\Example;
use LastDragon_ru\LaraASP\GraphQLPrinter\Contracts\DirectiveFilter;
use LastDragon_ru\LaraASP\GraphQLPrinter\Contracts\Printer;
use LastDragon_ru\LaraASP\GraphQLPrinter\Settings\DefaultSettings;
use Nuwave\Lighthouse\Schema\SchemaBuilder;

$schema   = app()->make(SchemaBuilder::class)->schema();
$printer  = app()->make(Printer::class);
$settings = new DefaultSettings();

$printer->setSettings(
    $settings->setDirectiveDefinitionFilter(
        new class() implements DirectiveFilter {
            #[Override]
            public function isAllowedDirective(string $directive, bool $isStandard): bool {
                return !in_array($directive, ['eq', 'all', 'find'], true);
            }
        },
    ),
);

Example::raw($printer->print($schema), 'graphql');
示例输出

$printer->print($schema)

"""
Use Input as Search Conditions for the current Builder.
"""
directive @searchBy
on
    | ARGUMENT_DEFINITION

directive @searchByOperatorAllOf
on
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorAnyOf
on
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorCondition
on
    | INPUT_FIELD_DEFINITION

directive @searchByOperatorContains
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorEndsWith
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorEqual
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorField
on
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorIn
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorLike
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorNot
on
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorNotContains
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorNotEndsWith
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorNotEqual
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorNotIn
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorNotLike
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorNotStartsWith
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

directive @searchByOperatorStartsWith
on
    | ENUM
    | INPUT_FIELD_DEFINITION
    | SCALAR

"""
Available conditions for `type User` (only one field allowed at a time).
"""
input SearchByConditionUser {
    """
    Field condition.
    """
    id: SearchByScalarID
    @searchByOperatorCondition

    """
    Field condition.
    """
    name: SearchByScalarString
    @searchByOperatorCondition
}

"""
Available conditions for `type User` (only one field allowed at a time).
"""
input SearchByRootUser {
    """
    All of the conditions must be true.
    """
    allOf: [SearchByRootUser!]
    @searchByOperatorAllOf

    """
    Any of the conditions must be true.
    """
    anyOf: [SearchByRootUser!]
    @searchByOperatorAnyOf

    """
    Field.
    """
    field: SearchByConditionUser
    @searchByOperatorField

    """
    Not.
    """
    not: SearchByRootUser
    @searchByOperatorNot
}

"""
Available operators for `scalar ID` (only one operator allowed at a time).
"""
input SearchByScalarID {
    """
    Equal (`=`).
    """
    equal: ID
    @searchByOperatorEqual

    """
    Within a set of values.
    """
    in: [ID!]
    @searchByOperatorIn

    """
    Not Equal (`!=`).
    """
    notEqual: ID
    @searchByOperatorNotEqual

    """
    Outside a set of values.
    """
    notIn: [ID!]
    @searchByOperatorNotIn
}

"""
Available operators for `scalar String` (only one operator allowed at a time).
"""
input SearchByScalarString {
    """
    Contains.
    """
    contains: String
    @searchByOperatorContains

    """
    Ends with a string.
    """
    endsWith: String
    @searchByOperatorEndsWith

    """
    Equal (`=`).
    """
    equal: String
    @searchByOperatorEqual

    """
    Within a set of values.
    """
    in: [String!]
    @searchByOperatorIn

    """
    Like.
    """
    like: String
    @searchByOperatorLike

    """
    Not contains.
    """
    notContains: String
    @searchByOperatorNotContains

    """
    Not ends with a string.
    """
    notEndsWith: String
    @searchByOperatorNotEndsWith

    """
    Not Equal (`!=`).
    """
    notEqual: String
    @searchByOperatorNotEqual

    """
    Outside a set of values.
    """
    notIn: [String!]
    @searchByOperatorNotIn

    """
    Not like.
    """
    notLike: String
    @searchByOperatorNotLike

    """
    Not starts with a string.
    """
    notStartsWith: String
    @searchByOperatorNotStartsWith

    """
    Starts with a string.
    """
    startsWith: String
    @searchByOperatorStartsWith
}

type Query {
    """
    Find a single user by an identifying attribute.
    """
    user(
        """
        Search by primary key.
        """
        id: ID
        @eq
    ): User
    @find

    """
    List multiple users.
    """
    users(
        where: SearchByRootUser
        @searchBy
    ): [User!]!
    @all
}

"""
Account of a person who utilizes this application.
"""
type User {
    """
    Unique primary key.
    """
    id: ID!

    """
    Non-unique name.
    """
    name: String!
}

测试断言

assertGraphQLIntrospectionEquals

比较默认公共模式(作为客户端通过 introspection 看到的模式)。

阅读更多.

assertGraphQLSchemaEquals

比较默认内部模式(包含所有指令)。

阅读更多.

assertGraphQLSchemaNoBreakingChanges

检查默认内部模式(包含所有指令)中是否存在破坏性更改。

阅读更多.

assertGraphQLSchemaNoDangerousChanges

检查默认内部模式(包含所有指令)中是否存在危险更改。

阅读更多.

assertGraphQLSchemaValid

验证默认内部模式(包含所有指令)。比 lighthouse:validate-schema 命令快,因为它只加载使用的指令。

阅读更多.

升级

请遵循 升级指南

贡献

本包是Laravel的“优秀包集”的一部分。请使用主仓库报告问题,发送拉取请求提问