似乎最近招聘正在逐渐增加?目的是在使用PHP的同时学习GraphQL。
TL; DR
我研究了GraphQL,并编写了一个支持PHP中的GraphQL的服务器应用程序。
我使用graphql-php作为库,但是用PHP表达模式以及如何检查类型非常困难。
由于GraphQL是与语言无关的规范,因此PHP端的架构定义肯定会在将来自动生成。
GraphQL是一个强制客户端返回API数据的规范,因此,如果您尝试支持GraphQL,则服务器端要满足客户端的要求将变得更加复杂或不灵活。
什么是GraphQL?
GraphQL是API的查询语言。如果SQL是用于从数据库检索数据的查询语言,则GraphQL是用于从API检索JSON的查询语言。
看看GraphQL
在GraphQL中,如果定义架构(定义类型和类型的字段),则其将是API规范。
用于以下架构定义
1 2 3 4 5 6 7 8 | type Query { me: User } type User { id: ID name: String } |
如果发出以下请求,则
1 2 3 4 5 | { me { name } } |
您将收到这样的答复。
1 2 3 4 5 | { "me": { "name": "Luke Skywalker" } } |
很容易理解,因为请求和响应格式非常相似。在这里,用户有两个ID和名称,但之所以只返回名称是因为在请求中仅指定了名称。
GraphQL
的好处
GraphQL具有许多优点。
有关更多信息,我认为您应该阅读本文。 https://qiita.com/bananaumai/items/3eb77a67102f53e8a1ad
之后,官方网站http://graphql.org/learn/
让我们编写一个接受GraphQL的服务器
想象一下一种允许用户发布文章的发布类型服务。
所有源代码都是https://github.com/kazuhei/graphql-sample
定义对象的架构
定义用户,标签和发布的架构。
图式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | type User { id: ID! name: String! age: Int } type Tag { name: String! } type Post { id: ID! title: String! contents: String! author: User! tags: [Tag]! } |
GraphQL具有一个称为ID的类型表达式,该表达式是字符串且唯一。
另外,
定义查询的架构
图式
1 2 3 4 5 | type Query { posts: [Post] popularPosts: [Post] post(id: ID): Post } |
您可以通过指定称为
查询的特殊类型来指定查询方法。
实施
这次,我们将使用一个名为https://github.com/webonyx/graphql-php的库来实现它。
准备调试环境
插入一个名为Chrome iQL的Chrome扩展程序,该扩展程序可以轻松重现GraphQL请求。
实施架构
我们将使用PHP类来表达模式。但是,大多数设置使用数组。
键入/ Post.php
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 | <?php namespace Type; use DataSource\TagDataSource; use DataSource\UserDataSource; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; class Post extends ObjectType { public function __construct() { $config = [ 'name' => 'Post', 'fields' => [ 'id' => [ 'type' =>Type::id(), ], 'title' => [ 'type' => Type::string(), ], 'contents' => [ 'type' => Type::string() ], 'author' => [ 'type' => User::getInstance(), ], 'tags' => Type::listOf(Tag::getInstance()) ], 'resolveField' => function ($value, $args, $context, ResolveInfo $info) { $method = 'resolve' . ucfirst($info->fieldName); if (method_exists($this, $method)) { return $this->{$method}($value, $args, $context, $info); } else { return $value->{$info->fieldName}; } } ]; parent::__construct($config); } private static $singleton; public static function getInstance(): self { return self::$singleton ? self::$singleton : self::$singleton = new self(); } public function resolveAuthor($value) { return UserDataSource::getById($value->authorId); } public function resolveTags($value) { return TagDataSource::getByPostId($value->id); } } |
类型/ User.php
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 | <?php namespace Type; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; class User extends ObjectType { public function __construct() { $config = [ 'name' => 'User', 'fields' => [ 'id' => Type::int(), 'name' => Type::string(), 'age' => Type::int(), ], ]; parent::__construct($config); } private static $singleton; public static function getInstance(): self { return self::$singleton ? self::$singleton : self::$singleton = new self(); } } |
类型/ Tag.php
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 | <?php namespace Type; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; class Tag extends ObjectType { public function __construct() { $config = [ 'name' => 'Tag', 'fields' => [ 'name' => [ 'type' => Type::string(), ], ], ]; parent::__construct($config); } private static $singleton; public static function getInstance(): self { return self::$singleton ? self::$singleton : self::$singleton = new self(); } } |
在$ config中使用php表示法定义graphql的架构。
graphql-php根据配置类型的类型定义类是否为同一实例来确定对象类型的身份,因此我们提供了一个单例函数getInstance,使其始终返回相同的实例。
$ config中的resolveField函数解析该字段的内容。我觉得这是GraphQL的重要组成部分,与准备常规API数据然后格式化并将其返回给API的情况不同,GraphQL希望准备数据,就像返回一样。
准备数据类
数据/ Post.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php namespace Data; class Post { // DBから取得可能 public $id; public $title; public $contents; public $authorId; // graphql-phpに上書きされる public $author; public $tags; public function __construct(string $id, string $title, string $contents, string $authorId) { $this->id = $id; $this->title = $title; $this->contents = $contents; $this->authorId = $authorId; } } |
graphql-php根据$ config的resolveField直接覆盖该字段以匹配GraphQL的响应,因此它是为预期而设计的。很辣由于尝试匹配GraphQL模式,PHP端的数据类的类型崩溃了……
如果您对其他数据类和服务器代码感兴趣,请访问https://github.com/kazuhei/graphql-sample
尝试移动
发出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 | query { # Postの一覧 posts { id title contents author { name age } tags { name } } # 人気Postのidとtitleだけを一覧で取得 popularPosts { id title } # PostをIDで取得 post(id: "2") { id title contents } } |
响应如下所示:
通常,它是从三个端点分别获取的内容,但是可以通过一个请求来获取。
result.json
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | { "data": { "posts": [ { "id": "1", "title": "first season", "contents": "...", "author": { "name": "Sophie Hojo", "age": 15 }, "tags": [ { "name": "Prism Stone" }, { "name": "SoLaMi Dressing" } ] }, { "id": "2", "title": "second season", "contents": "...", "author": { "name": "Mirei Minami", "age": 14 }, "tags": [ { "name": "armageddon" } ] }, { "id": "3", "title": "third season", "contents": "...", "author": { "name": "Laala Manaka", "age": 12 }, "tags": [ { "name": "nonsugar" } ] }, { "id": "4", "title": "4th season", "contents": "...", "author": { "name": "Laala Manaka", "age": 12 }, "tags": [ { "name": "DanPri" }, { "name": "FantasyTime" } ] } ], "popularPosts": [ { "id": "4", "title": "4th season" }, { "id": "3", "title": "third season" }, { "id": "1", "title": "first season" } ], "post": { "id": "2", "title": "second season", "contents": "..." } } } |
有趣的是
1 2 3 4 5 | # 人気Postのidとtitleだけを一覧で取得 popularPosts { id title } |
这是
部分。
在流行的帖子中,仅会获取ID和标题,而不会获取作者,因此作者获取过程不会在服务器端运行。与普通的API相比,我感到这很不愉快,而且我认为客户端主导着服务器端。
考虑吗?
关于GraphQL
我感兴趣的是实现每种类型都有其自己的获取方式,以便服务器可以处理客户端的每个请求。
结果,在服务器端,所有资源必须分别检索其数据。
具体而言,过去,通过将数据库的posts表和users表结合而获得的部分从posts表中获取数据,并针对每个post从users表中获取数据。这是一个典型的N 1问题。
为了解决这个问题,graphql-php建议使用两种类型的获取缓冲和异步通信,但是我发现两者都很复杂。可能不强制减少查询数量,而应将其放在内存缓存中,并尽量避免访问数据库。
我认为GraphQL端的模式定义以及如何编写与json结构匹配的请求都非常好,但是我觉得服务器端的实现会非常复杂。
关于graphql-php
仍然有一个版本为v0.11.4的地方,但是如果您输入有误,则可能由于内部服务器错误而无法理解原因,因此非常痛苦。