mxneyio / php-graphql-oqm
GraphQL 对象到查询映射器(QOM),可从 API 模式生成对象
Requires
- php: ^7.1 || ^8.0
- mxneyio/php-graphql-client: ^1.0
Requires (Dev)
- ext-json: *
- codacy/coverage: ^1.4
- phpunit/phpunit: ^7.5|^8.0
This package is not auto-updated.
Last update: 2024-09-18 19:49:15 UTC
README
此包利用 GraphQL API 的反射功能生成一组映射到 API 模式结构的类。然后,可以使用简单直观的方式使用生成的类查询 API 服务器。
使用 PHP 与 GraphQL API 交互从未如此简单!
安装
运行以下命令使用 composer 安装包
composer require gmostafa/php-graphql-oqm
生成模式对象
安装包后,第一步是生成模式对象。这可以通过执行以下命令轻松实现
php vendor/bin/generate_schema_objects
此脚本将使用 GraphQL 的反射功能检索 API 模式类型,然后从类型生成模式对象,并保存到包根目录下的 schema_object
目录中。您可以通过在运行命令时提供“自定义类写入目录”的值来覆盖默认写入目录。
用法
在下面的所有示例中,我将使用超级酷的公开 Pokemon GraphQL API 作为说明。
查看 API: https://graphql-pokemon.now.sh/
以及 Github 仓库: https://github.com/lucasbento/graphql-pokemon
在生成公开 Pokemon API 的模式对象之后,我们可以通过使用 RootQueryObject
轻松地查询 API。以下是一个示例
$rootObject = new RootQueryObject();
$rootObject
->selectPokemons((new RootPokemonsArgumentsObject())->setFirst(5))
->selectName()
->selectId()
->selectFleeRate()
->selectAttacks()
->selectFast()
->selectName();
此查询执行的操作是选择前 5 个宝可梦,返回它们的名称、id、逃跑率和带有名称的快速攻击。容易吗!?
剩下的只是实际运行查询以获取结果
$results = $client->runQuery($rootObject->getQuery());
有关如何使用客户端类的更多信息,请参阅
- https://github.com/mghoneimy/php-graphql-client#constructing-the-client
- https://github.com/mghoneimy/php-graphql-client#running-queries
注意
以下是一些关于模式对象的注意事项,以便在使用生成类时使您的生活更加轻松
处理对象选择器
虽然标量字段设置器返回当前查询对象的实例,但对象字段选择器返回嵌套查询对象的对象。这意味着将 $rootObject
引用设置为对象选择器返回的结果意味着根查询对象的引用已丢失。
不要这样做
$rootObject = (new RootQueryObject())->selectAttacks()->selectSpecial()->selectName();
这样,您将得到 PokemonAttackQueryObject
的引用,而 RootQueryObject
的引用已丢失。
这样做
$rootObjet = new RootQueryObject();
$rootObject->selectAttacks()->selectSpecial()->selectName();
这样,您可以跟踪 RootQueryObject
引用,并安全地开发查询。
处理多个对象选择器
假设我们想获取宝可梦“Charmander”,检索它的进化、进化要求和它的进化的进化要求,我们应该如何做到这一点?
我们不能这样做
$rootObject = new RootQueryObject();
$rootObject->selectPokemon(
(new RootPokemonArgumentsObject())->setName('charmander')
)
->selectEvolutions()
->selectName()
->selectNumber()
->selectEvolutionRequirements()
->selectName()
->selectAmount()
->selectEvolutionRequirements()
->selectName()
->selectAmount();
这是因为引用现在指向 Charmander 的进化的进化要求,而不是 Charmander 本身。
最好的方法是这样构建查询
$rootObject = new RootQueryObject();
$charmander = $rootObject->selectPokemon(
(new RootPokemonArgumentsObject())->setName('charmander')
);
$charmander->selectEvolutions()
->selectName()
->selectNumber()
->selectEvolutionRequirements()
->selectName()
->selectAmount();
$charmander->selectEvolutionRequirements()
->selectName()
->selectAmount();
这样,我们保留了 Charmander 的引用,并以直观的方式构建了查询。
通常,每当有一个分支(就像获取同一对象的进化及其进化要求的情况)时,最好的方法是像树一样构建查询,其中树的根是分支掉的对象的引用。在这种情况下,Charmander 是根,进化及其进化要求是两个从它分支出来的子树。
提高查询对象的可读性
关于如何使查询对象更易于阅读的一些建议
- 将作为根节点使用的节点存储在具有意义的变量中,就像charmander的情况一样。
- 将每个选择器单独写在单独的一行上。
- 每次使用对象选择器时,为下一个选择器添加额外的缩进。
- 将查询(如ArgumentsObject构造)中的新对象构造移动到新的一行。
模式对象生成
运行生成脚本后,SchemaInspector将在GraphQL服务器上运行查询以检索API模式。之后,SchemaClassGenerator将递归地遍历从根查询类型开始的模式,为模式规范中的每个对象创建一个类。
SchemaClassGenerator将根据以下从GraphQL类型到SchemaObject类型的映射生成不同的模式对象
- OBJECT:
QueryObject
- INPUT_OBJECT:
InputObject
- ENUM:
EnumObject
此外,将为每个对象中每个字段的参数生成一个ArgumentsObject
。参数对象命名约定是
{CURRENT_OBJECT}{FIELD_NAME}ArgumentsObject
QueryObject
对象生成器将从根queryType
开始遍历模式,根据以下规则为遇到的每个查询对象创建一个类
RootQueryObject
将为模式声明中的queryType
对应类型生成,此对象是所有GraphQL查询的起点。- 对于名称为{OBJECT_NAME}的查询对象,将创建一个名为
{OBJECT_NAME}QueryObject
的类。 - 对于查询对象选择集中每个选择字段,将创建一个相应的选择器方法,根据以下规则
- 标量字段将为其创建一个简单的选择器,将字段名称添加到选择集中。简单的选择器将返回正在创建的查询对象(this)的引用。
- 对象字段将为其创建一个对象选择器,该选择器将内部创建一个新的查询对象并将其嵌套在当前查询中。对象选择器将返回新创建的查询对象的实例。
- 对于与对象字段相关的每个参数列表,将根据以下规则创建一个
ArgumentsObject
和一个对应于每个参数值的setter- 标量参数:将为它们创建一个简单的setter以设置标量参数值。
- 列表参数:将为它们创建一个列表setter以使用
array
设置参数值。 - 输入对象参数:将为它们创建一个输入对象setter以使用类型为
InputObject
的对象设置参数值。
The InputObject
对于在遍历模式时遇到的每个输入对象,将根据以下规则创建相应的类
- 对于名称为{OBJECT_NAME}的输入对象,将创建一个名为
{OBJECT_NAME}InputObject
的类 - 对于输入对象声明中的每个字段,将创建一个setter,根据以下规则
- 标量字段:将为它们创建一个简单的setter以设置标量值。
- 列表字段:将为它们创建一个列表setter以使用
array
设置值。 - 输入对象参数:将为它们创建一个输入对象setter以使用类型为
InputObject
的对象设置值。
The EnumObject
对于在遍历模式时遇到的每个枚举,将根据以下规则创建相应的ENUM类
- 对于名称为{OBJECT_NAME}的枚举对象,将创建一个名为
{OBJECT_NAME}EnumObject
的类 - 对于ENUM声明中的每个EnumValue,将在类中创建一个const以存储其值
实时API示例
从根查询类型来看,这是Pokemon GraphQL API的模式结构
"queryType": {
"name": "Query",
"kind": "OBJECT",
"description": "Query any Pokémon by number or name",
"fields": [
{
"name": "query",
"type": {
"name": "Query",
"kind": "OBJECT",
"ofType": null
},
"args": []
},
{
"name": "pokemons",
"type": {
"name": null,
"kind": "LIST",
"ofType": {
"name": "Pokemon",
"kind": "OBJECT"
}
},
"args": [
{
"name": "first",
"description": null
}
]
},
{
"name": "pokemon",
"type": {
"name": "Pokemon",
"kind": "OBJECT",
"ofType": null
},
"args": [
{
"name": "id",
"description": null
},
{
"name": "name",
"description": null
}
]
}
]
}
我们基本有一个包含2个字段的根查询对象
- pokemons: 获取一系列的宝可梦对象。它有一个参数:first。
- pokemon: 获取一个宝可梦对象。它有两个参数:id和name。
将此模式小部分转换为3个对象
- RootQueryObject: 表示指向遍历API图的入口点
- RootPokemonsArgumentsObject: 表示在RootQueryObject中的"pokemons"字段上的参数列表
- RootPokemonArgumentsObject: 表示在RootQueryObject中的"pokemon"字段上的参数列表
以下是生成的3个类
<?php
namespace GraphQL\SchemaObject;
class RootQueryObject extends QueryObject
{
const OBJECT_NAME = "query";
public function selectPokemons(RootPokemonsArgumentsObject $argsObject = null)
{
$object = new PokemonQueryObject("pokemons");
if ($argsObject !== null) {
$object->appendArguments($argsObject->toArray());
}
$this->selectField($object);
return $object;
}
public function selectPokemon(RootPokemonArgumentsObject $argsObject = null)
{
$object = new PokemonQueryObject("pokemon");
if ($argsObject !== null) {
$object->appendArguments($argsObject->toArray());
}
$this->selectField($object);
return $object;
}
}
RootQueryObject
包含2个选择方法,每个字段一个,以及一个可选的参数,包含所需的ArgumentsObjects。
<?php
namespace GraphQL\SchemaObject;
class RootPokemonsArgumentsObject extends ArgumentsObject
{
protected $first;
public function setFirst($first)
{
$this->first = $first;
return $this;
}
}
RootPokemonsArgumentsObject
包含列表中"pokemons"字段的唯一参数作为一个属性,并提供一个设置器来更改其值。
<?php
namespace GraphQL\SchemaObject;
class RootPokemonArgumentsObject extends ArgumentsObject
{
protected $id;
protected $name;
public function setId($id)
{
$this->id = $id;
return $this;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
}
RootPokemonArgumentsObject
包含列表中"pokemon"字段的2个参数作为属性,并提供设置器来更改它们的值。
额外
此外,在递归遍历模式时将创建PokemonQueryObject
。它对于完成此演示不是必需的,但为了使事情更清晰,我将它添加到下面,以便有人想看到更多生成动作的话可以查看。
<?php
namespace GraphQL\SchemaObject;
class PokemonQueryObject extends QueryObject
{
const OBJECT_NAME = "Pokemon";
public function selectId()
{
$this->selectField("id");
return $this;
}
public function selectNumber()
{
$this->selectField("number");
return $this;
}
public function selectName()
{
$this->selectField("name");
return $this;
}
public function selectWeight(PokemonWeightArgumentsObject $argsObject = null)
{
$object = new PokemonDimensionQueryObject("weight");
if ($argsObject !== null) {
$object->appendArguments($argsObject->toArray());
}
$this->selectField($object);
return $object;
}
public function selectHeight(PokemonHeightArgumentsObject $argsObject = null)
{
$object = new PokemonDimensionQueryObject("height");
if ($argsObject !== null) {
$object->appendArguments($argsObject->toArray());
}
$this->selectField($object);
return $object;
}
public function selectClassification()
{
$this->selectField("classification");
return $this;
}
public function selectTypes()
{
$this->selectField("types");
return $this;
}
public function selectResistant()
{
$this->selectField("resistant");
return $this;
}
public function selectAttacks(PokemonAttacksArgumentsObject $argsObject = null)
{
$object = new PokemonAttackQueryObject("attacks");
if ($argsObject !== null) {
$object->appendArguments($argsObject->toArray());
}
$this->selectField($object);
return $object;
}
public function selectWeaknesses()
{
$this->selectField("weaknesses");
return $this;
}
public function selectFleeRate()
{
$this->selectField("fleeRate");
return $this;
}
public function selectMaxCP()
{
$this->selectField("maxCP");
return $this;
}
public function selectEvolutions(PokemonEvolutionsArgumentsObject $argsObject = null)
{
$object = new PokemonQueryObject("evolutions");
if ($argsObject !== null) {
$object->appendArguments($argsObject->toArray());
}
$this->selectField($object);
return $object;
}
public function selectEvolutionRequirements(PokemonEvolutionRequirementsArgumentsObject $argsObject = null)
{
$object = new PokemonEvolutionRequirementQueryObject("evolutionRequirements");
if ($argsObject !== null) {
$object->appendArguments($argsObject->toArray());
}
$this->selectField($object);
return $object;
}
public function selectMaxHP()
{
$this->selectField("maxHP");
return $this;
}
public function selectImage()
{
$this->selectField("image");
return $this;
}
}