graphql-php入门:如何从.graphql文件向架构添加解析器功能?

getting started in graphql-php: how to add resolver functions to schema from .graphql file?

我是GraphQL的新手,并且想与graphql-php一起使用,以构建一个入门的简单API。我目前正在阅读文档并尝试使用示例,但是起初我很困惑。

我希望将架构存储在schema.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
<?php
// graph-ql is installed via composer
require('../vendor/autoload.php');

use GraphQL\\Language\\Parser;
use GraphQL\\Utils\\BuildSchema;
use GraphQL\\Utils\\AST;
use GraphQL\\GraphQL;

try {
    $cacheFilename = 'cached_schema.php';
    // caching, as recommended in the docs, is disabled for testing
    // if (!file_exists($cacheFilename)) {
        $document = Parser::parse(file_get_contents('./schema.graphql'));
        file_put_contents($cacheFilename,"<?php\
return" . var_export(AST::toArray($document), true) . ';');
    /*} else {
        $document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
    }*/

    $typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
        // In the docs, this function is just empty, but I needed to return the $typeConfig, otherwise I got an error
        return $typeConfig;
    };
    $schema = BuildSchema::build($document, $typeConfigDecorator);

    $context = (object)array();

    // this has been taken from one of the examples provided in the repo
    $rawInput = file_get_contents('php://input');
    $input = json_decode($rawInput, true);
    $query = $input['query'];
    $variableValues = isset($input['variables']) ? $input['variables'] : null;
    $rootValue = ['prefix' => 'You said: '];
    $result = GraphQL::executeQuery($schema, $query, $rootValue, $context, $variableValues);
    $output = $result->toArray();
} catch (\\Exception $e) {
    $output = [
        'error' => [
            'message' => $e->getMessage()
        ]
    ];
}
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($output);

这是我的schema.graphql文件的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
schema {
    query: Query    
}

type Query {
    products: [Product!]!
}

type Product {
    id: ID!,
    type: ProductType
}

enum ProductType {
    HDRI,
    SEMISPHERICAL_HDRI,
    SOUND
}

我可以例如查询

1
2
3
query {
  __schema {types{name}}
}

并且这将按预期返回元数据。但是,当然,现在我想查询实际的产品数据,并从数据库中获取数据,为此,我需要定义一个解析器功能。

http://webonyx.github.io/graphql-php/type-system/type-language/上的文档说明:"默认情况下,这种架构是在没有任何解析器的情况下创建的。我们必须依靠默认字段解析器和根值以便针对该架构执行查询。" -但是没有这样做的例子。

如何为每种类型/字段添加解析器功能?


此方法无需实例化服务器即可工作。 就我而言,我已经有一个服务器并且可以读取HTTP数据,我所需要做的就是读取GraphQL模式并运行查询。 首先,我从文件中读取模式:

1
2
3
4
5
        $schemaContent = // file_get_contents or whatever works for you

        $schemaDocument = GraphQL\\Language\\Parser::parse($schemaContent);
        $schemaBuilder = new GraphQL\\Utils\\BuildSchema($schemaDocument);
        $schema = $schemaBuilder->buildSchema();

然后,我通过自定义字段解析器执行查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
        $fieldResolver = function() {
            return call_user_func_array([$this, 'defaultFieldResolver'], func_get_args());
        };

        $result = GraphQL\\GraphQL::executeQuery(
            $schema,
            $query,        // this was grabbed from the HTTP post data
            null,
            $appContext,   // custom context
            $variables,    // this was grabbed from the HTTP post data
            null,
            $fieldResolver // HERE, custom field resolver
        );

字段解析器如下所示:

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
private static function defaultFieldResolver(
    $source,
    $args,
    $context,
    \\GraphQL\\Type\\Definition\
esolveInfo $info
) {
    $fieldName = $info->fieldName;
    $parentType = $info->parentType->name;

    if ($source === NULL) {
        // this is the root value, return value depending on $fieldName
        // ...
    } else {
        // Depending on field type ($parentType), I call different field resolvers.
        // Since our system is big, we implemented a bootstrapping mechanism
        // so modules can register field resolvers in this class depending on field type
        // ...

        // If no field resolver was defined for this $parentType,
        // we just rely on the default field resolver provided by graphql-php (copy/paste).
        $fieldName = $info->fieldName;
        $property = null;

        if (is_array($source) || $source instanceof \\ArrayAccess) {
            if (isset($source[$fieldName])) {
                $property = $source[$fieldName];
            }
        } else if (is_object($source)) {
            if (isset($source->{$fieldName})) {
                $property = $source->{$fieldName};
            }
        }

        return $property instanceof \\Closure
            ? $property($source, $args, $context)
            : $property;
    }
}

这就是我最终要做的...

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
$rootResolver = array(
    'emptyCart' => function($root, $args, $context, $info) {
        global $rootResolver;
        initSession();
        $_SESSION['CART']->clear();
        return $rootResolver['getCart']($root, $args, $context, $info);
    },
    'addCartProduct' => function($root, $args, $context, $info) {
        global $rootResolver;

        ...

        return $rootResolver['getCart']($root, $args, $context, $info);
    },
    'removeCartProduct' => function($root, $args, $context, $info) {
        global $rootResolver;

        ...

        return $rootResolver['getCart']($root, $args, $context, $info);
    },
    'getCart' => function($root, $args, $context, $info) {
        initSession();
        return array(
            'count' => $_SESSION['CART']->quantity(),
            'total' => $_SESSION['CART']->total(),
            'products' => $_SESSION['CART']->getProductData()
        );
    },

然后在配置中

1
2
3
4
5
6
7
8
9
$config = ServerConfig::create()
    ->setSchema($schema)
    ->setRootValue($rootResolver)
    ->setContext($context)
    ->setDebug(DEBUG_MODE)
    ->setQueryBatching(true)
;

$server = new StandardServer($config);

在我看来,这有点hacking,我可能应该将解析程序外包到单独的文件中,但是它可以正常工作……仍然感到困惑的是,没有简单的示例可以完成此任务,也许比我的解决方案更好。


我为此使用根值:

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
<?php

require("vendor/autoload.php") ;
require("exemplo-graphql.php");
require("Usuario.php");

use GraphQL\\GraphQL;
use GraphQL\\Type\\Schema;
use GraphQL\\Utils\\BuildSchema;

$query = $_REQUEST['query'];

$typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
    $name = $typeConfig['name'];
    // ... add missing options to $typeConfig based on type $name
    return $typeConfig;
};

$contents = file_get_contents('schema.graphql');
$schema = BuildSchema::build($contents, $typeConfigDecorator);

// $rawInput = file_get_contents('php://input');
$input = json_decode($query, true);
$query = $input['query'];
$variableValues = isset($input['variables']) ? $input['variables'] : null;

try {
    // $rootValue = ['prefix' => 'You said: '];
    $rootValue = [
        'usuario' => function($root, $args, $context, $info) {
            $usuario = new Usuario();
            $usuario->setNome("aqui tem um teste");
            $usuario->setEmail("aqui tem um email");
            return $usuario;
        },
        'echo' => function($root, $args, $context, $info) {
            return"aqui tem um echooo";
        },
        'adicionarUsuario' => function ($root, $args, $context, $info) {
            $usuario = new Usuario();
            $usuario->setNome("aqui tem um teste");
            $usuario->setEmail("aqui tem um email");
            return $usuario;
        }
    ];

    $result = GraphQL::executeQuery($schema, $query, $rootValue, null,
        $variableValues);

    if ($result->errors) {
        $output = [
            'errors' => [
                [
                    'message' => $result->errors
                ]
            ]
    ];
    } else {
        $output = $result->toArray();
    }
} catch (\\Exception $e) {
    $output = [
        'errors' => [
            [
                'message' => $e->getMessage()
            ]
        ]
    ];
}

header('Content-Type: application/json');
echo json_encode($output);