stefanorg/graphql-middleware

容器互操作性的 GraphQL 中间件

dev-master 2019-06-18 17:06 UTC

This package is not auto-updated.

Last update: 2024-09-24 23:26:20 UTC


README

这是一个基于 Youshido/GraphQL 的 GraphQL 纯 PHP 实现的 GraphQL 中间件实现。

使用中间件

use GraphQLMiddleware\Execution\Processor;
use App\GraphQL\MySchema;
use GraphQLMiddleware\GraphQLMiddleware;

...
...

//instantiate the graphql schema
$schema = new MySchema();

//get your container implementing container-interop
$container = get_your_container;

//init processor
$processor = new Processor($container, $schema);
$graphqlMiddleware = new GraphQLMiddleware($processor)

$app->pipe("/graphql", $graphqlMiddleware)

如果您的容器支持工厂,您可以使用提供的那些。

例如,如果您使用 zend expressive,您可以通过 配置管理器 利用提供的配置包。

$configManager = new ConfigManager([
    GraphQLMiddleware\ModuleConfig::class,
    new PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
]);

与 GraphQL 中间件交互

对于针对 /graphql URI 或包含 Content-Type: application/graphql 标头的每个请求。目前仅支持 GETPOST 方法。

工厂

  • Schema\SchemaFactory 工厂用于从数组、类或容器中实例化模式对象
  • Execution\ProcessorFactory 工厂用于实例化 GraphQL 处理器,具有 ContainerAwareInterface 能力,允许您编写带有直接从容器中获取服务能力的解析器类。
  • Resolver\ContainerAwareResoverFactory 工厂用于自动将容器注入到您的解析器类中

容器感知字段

您可以在您的模式中编写使字段感知容器的代码,这样您可以直接从字段中检索容器引用,并从容器中拉取任何您需要用于 resolve 字段逻辑的依赖项。

假设我们有一个 TodoField,我们正在实现一个用于将待办事项存储到我们后端的突变 AddTodoField


<?php

namespace App\GraphQL\Mutation\Todo;


use App\GraphQL\Type\TodoType;
use App\Service\TodoService;
use GraphQLMiddleware\Field\AbstractContainerAwareField;
use Youshido\GraphQL\Config\Field\FieldConfig;
use Youshido\GraphQL\Execution\ResolveInfo;
use Youshido\GraphQL\Type\ListType\ListType;
use Youshido\GraphQL\Type\NonNullType;
use Youshido\GraphQL\Type\Scalar\StringType;

class AddTodoField extends AbstractContainerAwareField
{
    public function build(FieldConfig $config)
    {
        $config->addArguments([
            'title' => new NonNullType(new StringType()),
            'tags' => new ListType(new StringType())
        ]);
    }

    public function resolve($value, array $args, ResolveInfo $info)
    {
        // pull our service from the db
        $todoService = $this->getContainer()->get(TodoService::class);
        // let the service do his job
        return $todoService->create($args['title']);
    }


    public function getType()
    {
        return new ListType(new TodoType());
    }

    public function getName()
    {
        return 'add';
    }
}

为了从我们的服务中检索待办事项,我们可以这样编写查询字段


namespace App\GraphQL\Query\Todo;


use App\GraphQL\Type\TodoType;
use App\Serivice\TodoService;
use GraphQLMiddleware\Field\AbstractContainerAwareField;
use Youshido\GraphQL\Execution\ResolveInfo;
use Youshido\GraphQL\Type\ListType\ListType;


class TodosField extends AbstractContainerAwareField
{
    public function getType()
    {
        return new ListType(new TodoType());
    }

    public function resolve($value, array $args, ResolveInfo $info)
    {
        /** @var $service TodoService */
        $service = $this->getContainer()->get(TodoService::class);
        return $service->findAll();
    }
}

业务逻辑验证

GraphQL 提供了对模式类型的验证,但目前没有一条清晰的路径来执行应用程序验证。

验证用户数据之前将其发送到您的后端是您的责任。让我们想象在我们的待办事项示例中,title 字段不能包含空格,且长度必须小于 10 个字符。

GraphQLMiddleware 提供了一些直接在您的 type 中调用 resolve 方法之前执行应用程序验证的便利性。

为此,您可以实现 GraphQLMiddleware\Validation\ValidatableInterface。此接口已经在 AbstractContainerAwareField 中实现。

让我们修改我们的突变字段 AddTodoField 以对 title 字段实施验证要求。

GraphQLMiddleware 使用 respect/validation 库来处理验证。

为了修改我们的 AddTodoField,我们必须

  • 提供验证规则,实现 ValidatableInterface::getValidationRules 方法
use Respect\Validation\Validator as v;

class AddTodoField extends AbstractContainerAwareField
{

    ...
    ...
    
    public function resolve($value, array $args, ResolveInfo $info)
    {
        // the $args array is validate based on the
        // validation rules provided by the getValidationRules method
        return $this->getContainer()->get(TodoResolver::class)->create($args['title']);
    }
    
    public function getValidationRules()
    {
        return [
            'title' => v::stringType()->alpha()->length(1,10)->noWhitespace()
        ];
    }
    
    ...
    ...

}

这样,处理器在实际上调用 resolve 方法之前,会调用 validate 方法,并且只有当一切顺利时才会调用 resolve 方法。

AbstractContainerAwareField 提供了 validate 方法的默认实现。因此,您不需要实现它,只需提供验证规则即可。

验证错误响应

服务器在出现验证错误时提供了关于这些错误的有用信息。实际的实现将把这些信息推送到 errors 有效负载中,如下所示

有人认为 errors 有效负载是为了系统错误,如 语法 错误,但个人认为这是一个放置关于请求正在发生什么的好地方。

使用我们的 todos 示例,如果我们尝试添加待办事项

mutation{
    add(title: "this is not @ very good title") {
        id
    }
}

服务器响应是

{
  "data": {
    "add": null
  },
  "errors": [
    {
      "message": "Bad Request. Validation failed.",
      "validation_errors": {
        "add": {
          "title": [
            "\"this is not @ very good title\" alphanumeric only allowed",
            "\"this is not @ very good title\" no withespace allowed"
          ]
        }
      },
      "code": 403
    }
  ]
}

errors 有效载荷中,我们包含了 validation_errors 信息,这些信息为我们提供了关于请求中错误的详细信息。