graphql php

其他类别 2025-08-20

GraphQl

这是基于JavaScript参考实现的GraphQL规范的PHP实现。

相关项目

  • DateTime标量
  • 继电器支持

要求

  • PHP版本> = 7.1
  • EXT-MBSTRING

目录

  • 安装
  • 例子
  • 创建模式
    • 解析器注册表
    • 解析器中间件
  • 执行
    • 查询
    • 解析器
      • N+1问题
    • 变量
    • 语境
  • 标量
    • 自定义标量
  • 高级用法
  • 一体化
    • 拉拉维尔

安装

运行以下命令通过Composer安装软件包:

composer require digiaonline/graphql

例子

这是一个简单的示例,该示例演示了如何从GraphQl架构文件中构建可执行模式,该文件包含以星球大战为主题的架构的架构定义语言(SDL)(有关架构定义本身,请参见下文)。在此示例中,我们使用该SDL来构建可执行模式,并使用它来查询英雄名称。该查询的结果是一个关联阵列,其结构类似于我们运行的查询。

 use Digia  GraphQL  Language  FileSourceBuilder ;
use function Digia  GraphQL  buildSchema ;
use function Digia  GraphQL  graphql ;

$ sourceBuilder = new FileSourceBuilder ( __DIR__ . ' /star-wars.graphqls ' );

$ schema = buildSchema ( $ sourceBuilder -> build (), [
    ' Query ' => [
        ' hero ' => function ( $ rootValue , $ arguments ) {
            return getHero ( $ arguments [ ' episode ' ] ?? null );
        },
    ],
]);

$ result = graphql ( $ schema , '
query HeroNameQuery {
  hero {
    name
  }
} ' );

print_r ( $ result );

上面的脚本产生以下输出:

 Array
(
    [data] => Array
    (
        [hero] => Array
        (
            [name] => " R2-D2 "
        )
        
    )
    
)

此示例中使用的GraphQL架构文件包含以下内容:

 schema {
    query : Query
}

type Query {
    hero ( episode : Episode ): Character
    human ( id : String ! ): Human
    droid ( id : String ! ): Droid
}

interface Character {
    id : String !
    name : String
    friends : [ Character ]
    appearsIn : [ Episode ]
}

type Human implements Character {
    id : String !
    name : String
    friends : [ Character ]
    appearsIn : [ Episode ]
    homePlanet : String
}

type Droid implements Character {
    id : String !
    name : String
    friends : [ Character ]
    appearsIn : [ Episode ]
    primaryFunction : String
}

enum Episode { NEWHOPE , EMPIRE , JEDI }

创建模式

为了针对您的GraphQl API执行查询,您首先需要定义API的结构。这是通过创建模式来完成的。有两种方法可以这样做,您可以使用SDL进行操作,也可以通过编程方式进行操作。但是,我们强烈建议您使用SDL,因为它更容易使用。要从SDL制作可执行模式,您需要调用buildSchema函数。

buildSchema函数需要三个参数:

  • $source架构定义(SDL)作为Source实例
  • $resolverRegistry一个关联阵列或包含所有解析器的ResolverRegistry实例
  • $options构建模式的选项,其中还包括自定义类型和指令

要创建Source实例,您可以使用提供的FileSourceBuilderMultiFileSourceBuilder类。

解析器注册表

解析器注册表本质上是一张平面地图,其类型名称为键及其相应的解析器实例作为其值。对于较小的项目,您可以使用关联数组和Lambda功能来定义您的解析器注册表。但是,在较大的项目中,我们建议您实施自己的解析器。您可以在“解析器”部分下阅读有关解析器的更多信息。

关联阵列示例:

 $ schema = buildSchema ( $ source , [
    ' Query ' => [
        ' hero ' => function ( $ rootValue , $ arguments ) {
            return getHero ( $ arguments [ ' episode ' ] ?? null );
        },
    ],
]);

解析器类示例:

 $ schema = buildSchema ( $ source , [
    ' Query ' => [
        ' hero ' => new HeroResolver (),
    ],
]);

解析器中间件

如果您发现自己在多个解析器中编写相同的逻辑,则应考虑使用中间件。解析器中间件允许您有效地管理多个解析器的功能。

中间件示例之前:

 $ resolverRegistry = new ResolverRegristry ([
    ' Query ' => [
        ' hero ' => function ( $ rootValue , $ arguments ) {
            return getHero ( $ arguments [ ' episode ' ] ?? null );
        },
    ],
], [
    ' middleware ' => [ new BeforeMiddleware ()],
]);
$ schema = buildSchema ( $ source , $ resolverRegistry );
 class BeforeMiddleware implements ResolverMiddlewareInterface
{
    public function resolve ( callable $ resolveCallback , $ rootValue , array $ arguments , $ context , ResolveInfo $ info ) {
        $ newRootValue = $ this -> doSomethingBefore ();
        return $ resolveCallback ( $ newRootValue , $ arguments , $ context , $ info );
    }
}

中间件之后示例:

 $ resolverRegistry = new ResolverRegristry ([
    ' Query ' => [
        ' hero ' => function ( $ rootValue , $ arguments ) {
            return getHero ( $ arguments [ ' episode ' ] ?? null );
        },
    ],
], [
    ' middleware ' => [ new AfterMiddleware ()],
]);
$ schema = buildSchema ( $ source , $ resolverRegistry );
 class AfterMiddleware implements ResolverMiddlewareInterface
{
    public function resolve ( callable $ resolveCallback , $ rootValue , array $ arguments , $ context , ResolveInfo $ info ) {
        $ result = $ resolveCallback ( $ rootValue , $ arguments , $ context , $ info );
        $ this -> doSomethingAfter ();
        return $ result ;
    }
}

解析器中间件对于许多事情都有用。例如记录,输入消毒,性能测量,授权和缓存。

如果您想了解有关模式的更多信息,则可以参考规范。

执行

查询

要执行针对您的架构的查询,您需要调用graphql函数并将其传递您的模式以及您希望执行的查询。您还可以通过更改查询来运行突变订阅

 $ query = '
query HeroNameQuery {
  hero {
    name
  }
} ' ;

$ result = graphql ( $ schema , $ query );

如果您想了解有关查询的更多信息,则可以参考规范。

解析器

模式中的每种类型都有与之关联的解析器,可以解决实际值。但是,大多数类型不需要自定义解析器,因为它们可以使用默认的解析器解决。通常,这些解析器是lambda功能,但是您也可以通过扩展AbstractTypeResolverAbstractFieldResolver来定义自己的解析器。另外,您还可以直接实现ResolverInterface

解析器功能会收到四个参数:

  • $rootValue父对象,在某些情况下也可以null
  • $arguments查询中提供给字段的参数
  • $context一个传递给每个可以保存重要上下文信息的解析器的值
  • $info一个值,该值包含与当前查询相关的特定领域信息

lambda功能示例:

 function ( $ rootValue , array $ arguments , $ context , ResolveInfo $ info ): string {
    return [
        ' type '       => ' Human ' ,
        ' id '         => ' 1000 ' ,
        ' name '       => ' Luke Skywalker ' ,
        ' friends '    => [ ' 1002 ' , ' 1003 ' , ' 2000 ' , ' 2001 ' ],
        ' appearsIn '  => [ ' NEWHOPE ' , ' EMPIRE ' , ' JEDI ' ],
        ' homePlanet ' => ' Tatooine ' ,
    ];
}

类型解析器示例:

 class HumanResolver extends AbstractTypeResolver
{
    public function resolveName ( $ rootValue , array $ arguments , $ context , ResolveInfo $ info ): string
    {
        return $ rootValue [ ' name ' ];
    }
}

现场解析器示例:

 class NameResolver extends AbstractFieldResolver
{
    public function resolve ( $ rootValue , array $ arguments , $ context , ResolveInfo $ info ): string
    {
       return $ rootValue [ ' name ' ];
    }
}

N+1问题

解析器功能可以返回值,承诺或一系列承诺。下面的该解析器功能说明了如何使用承诺解决N+1问题,可以在此测试案例中找到完整的示例。

 $ movieType = newObjectType ([
    ' fields ' => [
        ' title '    => [ ' type ' => stringType ()],
        ' director ' => [
            ' type '    => $ directorType ,
            ' resolve ' => function ( $ movie , $ args ) {
                DirectorBuffer:: add ( $ movie [ ' directorId ' ]);
                
                return new Promise ( function ( callable $ resolve , callable $ reject ) use ( $ movie ) {
                    DirectorBuffer:: loadBuffered ();
                    $ resolve (DirectorBuffer:: get ( $ movie [ ' directorId ' ]));
                });
            }
        ]
    ]
]);

变量

通过将查询传递到graphql函数时,您可以通过变量传递。

 $ query = '
query HeroNameQuery($id: ID!) {
  hero(id: $id) {
    name
  }
} ' ;

$ variables = [ ' id ' => ' 1000 ' ];

$ result = graphql ( $ schema , $ query , null , null , $ variables );

语境

如果您需要将一些重要的上下文信息传递给查询,则可以使用graphql上的$contextValues参数进行此操作。这些数据将作为$context参数传递给所有解析器。

 $ contextValues = [
    ' currentlyLoggedInUser ' => $ currentlyLoggedInUser ,
];

$ result = graphql ( $ schema , $ query , null , $ contextValues , $ variables );

标量

架构中的叶节点称为标量,每个标量都分解为某些具体数据。 GraphQL中的内置或指定标量如下:

  • 布尔
  • 漂浮
  • int
  • ID
  • 细绳

自定义标量

除指定的标量外,您还可以定义自己的自定义标量,并通过将它们传递到buildSchema函数作为$options参数的一部分,让您的模式知道它们。

自定义日期标量类型示例:

 $ dateType = newScalarType ([
    ' name '         => ' Date ' ,
    ' serialize '    => function ( $ value ) {
        if ( $ value instanceof DateTime) {
            return $ value -> format ( ' Y-m-d ' );
        }
        return null ;
    },
    ' parseValue '   => function ( $ value ) {
        if ( is_string ( $ value )){
            return new DateTime ( $ value );
        }
        return null ;
    },
    ' parseLiteral ' => function ( $ node ) {
        if ( $ node instanceof StringValueNode) {
            return new DateTime ( $ node -> getValue ());
        }
        return null ;
    },
]);

$ schema = buildSchema ( $ source , [
    ' Query ' => QueryResolver::class,
    [
        ' types ' => [ $ dateType ],
    ],
]);

每个标量都必须强制强制,这是由三个不同功能完成的。 serialize函数将PHP值转换为相应的输出值。 parseValue函数将变量输入值转换为相应的PHP值,而parseLiteral函数将AST字面函数转换为相应的PHP值。

高级用法

如果您正在寻找此文档尚未涵盖的内容,那么最好的选择就是看该项目中的测试。您会惊讶于您在那里找到多少个例子。

一体化

拉拉维尔

这是一个示例,演示了如何在Laravel项目中使用此库。您需要一个应用程序服务来将此库曝光到您的应用程序,一个服务提供商可以注册该服务,控制器和处理GraphQl Post请求的路由。

app/graphql/graphqlservice.php

 class GraphQLService
{
    private $ schema ;

    public function __construct ( Schema $ schema )
    {
        $ this -> schema = $ schema ;
    }

    public function executeQuery ( string $ query , array $ variables , ? string $ operationName ): array
    {
        return graphql ( $ this -> schema , $ query , null , null , $ variables , $ operationName );
    }
}

app/graphql/graphqlServiceProvider.php

 class GraphQLServiceProvider
{
    public function register ()
    {
        $ this -> app -> singleton (GraphQLService::class, function () {
            $ schemaDef = file_get_contents ( __DIR__ . ' /schema.graphqls ' );

            $ executableSchema = buildSchema ( $ schemaDef , [
                ' Query ' => QueryResolver::class,
            ]);

            return new GraphQLService ( $ executableSchema );
        });
    }
}

app/graphql/graphqlcontroller.php

 class GraphQLController extends Controller
{
    private $ graphqlService ;

    public function __construct ( GraphQLService $ graphqlService )
    {
        $ this -> graphqlService = $ graphqlService ;
    }

    public function handle ( Request $ request ): JsonResponse
    {
        $ query         = $ request -> get ( ' query ' );
        $ variables     = $ request -> get ( ' variables ' ) ?? [];
        $ operationName = $ request -> get ( ' operationName ' );

        $ result = $ this -> graphqlService -> executeQuery ( $ query , $ variables , $ operationName );

        return response ()-> json ( $ result );
    }
}

路由/api.php

Route:: post ( ' /graphql ' , ' appGraphQLGraphQLController@handle ' );

贡献者

由于所有贡献的人,该项目的存在。贡献。

支持者

感谢我们所有的支持者!成为支持者

赞助商

通过成为赞助商来支持这个项目。您的徽标将在此处显示您网站的链接。成为赞助商

执照

请参阅许可证。

下载源码

通过命令行克隆项目:

git clone https://github.com/digiaonline/graphql-php.git