GraphQL
這是基于 JavaScript 參考實(shí)現(xiàn)的GraphQL規(guī)范的PHP 實(shí)現(xiàn)。
相關(guān)項(xiàng)目
要求
- PHP版本> = 7.1
- EXT-MBSTRING
安裝
運(yùn)行以下命令以通過Composer安裝程序包:
composer require digiaonline/graphql
例
下面是一個(gè)簡(jiǎn)單的示例井氢,演示如何從GraphQL模式文件構(gòu)建可執(zhí)行模式,該文件包含
星球大戰(zhàn)主題模式的Schema
定義語言(SDL)(Schema
定義本身毕骡,見下文)。在
此示例中岩瘦,我們使用該SDL構(gòu)建可執(zhí)行模式未巫,并使用它來查詢英雄的名稱。該
查詢的結(jié)果是一個(gè)關(guān)聯(lián)數(shù)組启昧,其結(jié)構(gòu)類似于我們運(yùn)行的查詢橱赠。
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);
上面的腳本產(chǎn)生以下輸出:
Array
(
[data] => Array
(
[hero] => Array
(
[name] => "R2-D2"
)
)
)
此示例中使用的GraphQL架構(gòu)文件包含以下內(nèi)容:
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 }
創(chuàng)建架構(gòu)
要對(duì)GraphQL API執(zhí)行查詢,首先需要定義API的結(jié)構(gòu)箫津。這是
通過創(chuàng)建模式來完成的狭姨。有兩種方法可以執(zhí)行此操作,您可以使用SDL執(zhí)行此操作苏遥,也可以通過編程方式執(zhí)行此操作饼拍。
但是,我們強(qiáng)烈建議您使用SDL田炭,因?yàn)樗子谑褂檬ΤR獜?br>
SDL 創(chuàng)建可執(zhí)行Schema
,您需要調(diào)用該buildSchema
函數(shù)教硫。
該buildSchema
函數(shù)有三個(gè)參數(shù):
-
$source``Schema
定義(SDL)作為Source
實(shí)例 -
$resolverRegistry
關(guān)聯(lián)數(shù)組或ResolverRegistry
包含所有解析器的實(shí)例 -
$options
用于構(gòu)建Schema
的選項(xiàng)叨吮,其中還包括自定義類型和指令
要?jiǎng)?chuàng)建Source
實(shí)例,您可以使用提供的FileSourceBuilder
或MultiFileSourceBuilder
類瞬矩。
Resolver registry
Resolver registry
本質(zhì)上是一個(gè)簡(jiǎn)單的映射茶鉴,其類型名稱將作為其鍵,其對(duì)應(yīng)的解析器
實(shí)例將作為其值景用。對(duì)于較小的項(xiàng)目涵叮,您可以使用關(guān)聯(lián)數(shù)組和lambda函數(shù)(即:匿名函數(shù))來定義
Resolver registry
。但是伞插,在較大的項(xiàng)目中割粮,我們建議您實(shí)現(xiàn)自己的解析器。您可以
在“ 解析器”(#Resolvers)部分下閱讀有關(guān)解析器的更多信息媚污。
關(guān)聯(lián)數(shù)組示例:
$schema = buildSchema($source, [
'Query' => [
'hero' => function ($rootValue, $arguments) {
return getHero($arguments['episode'] ?? null);
},
],
]);
解析器類示例:
$schema = buildSchema($source, [
'Query' => [
'hero' => new HeroResolver(),
],
]);
解析器中間件
如果您發(fā)現(xiàn)自己在多個(gè)解析器中編寫相同的邏輯舀瓢,則應(yīng)考慮使用中間件。解析器
中間件允許您跨多個(gè)解析器有效地管理功能耗美。
在中間件示例之前:
// use Digia\GraphQL\Schema\Resolver\ResolverRegistry;
$resolves=new ResolverRegistry([
'Query' => [
'hero' => function ($rootValue, $arguments) {
return getHero($arguments['episode'] ?? null);
},
],
], [
new BeforeMiddleware()
])
$schema = buildSchema($source,$resolves);
class BeforeMiddleware implements ResolverMiddlewareInterface
{
public function resolve(callable $resolveCallback, $rootValue, array $arguments, $context, ResolveInfo $info) {
$newRootValue = $this->doSomethingBefore();
return $resolveCallback($newRootValue, $arguments, $context, $info);
}
}
中間件示例后:
// use Digia\GraphQL\Schema\Resolver\ResolverRegistry;
$resolves=new ResolverRegistry([
'Query' => [
'hero' => function ($rootValue, $arguments) {
return getHero($arguments['episode'] ?? null);
},
],
], [
new AfterMiddleware()
])
$schema = buildSchema($source,$resolves);
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;
}
}
解析器中間件可用于許多事情; 例如日志記錄京髓,輸入清理航缀,性能
測(cè)量,授權(quán)和緩存朵锣。
如果您想了解有關(guān)Schema
的更多信息,可以參考規(guī)范甸私。
執(zhí)行
Queries
要對(duì)Schema
執(zhí)行查詢诚些,您需要調(diào)用該graphql
函數(shù)并將其Schema
和要執(zhí)行的查詢傳遞給它
。您還可以通過更改查詢來運(yùn)行Mutations
和subscriptions
皇型。
$query = '
query HeroNameQuery {
hero {
name
}
}';
$result = graphql($schema, $query);
如果您想了解有關(guān)查詢的更多信息诬烹,可以參考規(guī)范。
Resolvers
Schema
中的每個(gè)類型都有一個(gè)與之關(guān)聯(lián)的解析器(Resolvers
)弃鸦,允許解析實(shí)際值绞吁。但是,大多數(shù)
類型不需要自定義解析器唬格,因?yàn)榭梢允褂媚J(rèn)解析器解析它們家破。通常這些解析器
是lambda
函數(shù)(即:匿名函數(shù)),但您也可以通過擴(kuò)展AbstractTypeResolver
或AbstractFieldResolver
來定義自己的解析器购岗√或者自行實(shí)現(xiàn)ResolverInterface
。
解析器函數(shù)接收四個(gè)參數(shù):
-
$rootValue
父對(duì)象喊积,在某些情況下可為null
-
$arguments
在查詢中提供給該字段的參數(shù) -
$context
傳遞給每個(gè)可以保存重要上下文信息的解析器的值 -
$info
保存與當(dāng)前查詢相關(guān)的字段特定信息的值
Lambda函數(shù)示例:
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',
];
}
Type解析器示例:
class HumanResolver extends AbstractTypeResolver
{
public function resolveName($rootValue, array $arguments, $context, ResolveInfo $info): string
{
return $rootValue['name'];
}
}
Field解析器示例:
class NameResolver extends AbstractFieldResolver
{
public function resolve($rootValue, array $arguments, $context, ResolveInfo $info): string
{
return $rootValue['name'];
}
}
N + 1問題
解析器函數(shù)可以返回“值”烹困,promise或promise數(shù)組。
下面的解析器函數(shù)說明了如何使用promise來解決N + 1問題乾吻,完整的例子可以在
這個(gè)測(cè)試用例中找到髓梅。
$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']));
});
}
]
]
]);
Variables
通過將查詢傳遞給graphql
函數(shù),可以在執(zhí)行查詢時(shí)傳入變量绎签。
$query = '
query HeroNameQuery($id: ID!) {
hero(id: $id) {
name
}
}';
$variables = ['id' => '1000'];
$result = graphql($schema, $query, null, null, $variables);
Context
如果您需要將一些重要的上下文信息傳遞給查詢枯饿,可以使用$contextValues
參數(shù)graphql
來執(zhí)行此操作。此數(shù)據(jù)將作為$context
參數(shù)傳遞給所有解析器诡必。
$contextValues = [
'currentlyLoggedInUser' => $currentlyLoggedInUser,
];
$result = graphql($schema, $query, null, $contextValues, $variables);
Scalars
模式中的葉節(jié)點(diǎn)稱為標(biāo)量(Scalars
)鸭你,每個(gè)標(biāo)量(Scalars
)可以解析出一些具體數(shù)據(jù)。
GraphQL中的內(nèi)置或指定的標(biāo)量如下:
- Boolean
- Float
- Int
- ID
- String
自定義標(biāo)量
除了指定的標(biāo)量之外擒权,您還可以定義自己的自定義標(biāo)量袱巨,
并通過將它們傳遞給buildSchema
函數(shù)作為$options
的一部分參數(shù)來讓您的Schema
了解它們。
自定義日期標(biāo)量類型示例:
$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],
],
]);
每個(gè)標(biāo)量都必須被強(qiáng)制執(zhí)行碳抄,由三個(gè)不同的函數(shù)完成愉老。serialize
函數(shù)將
PHP值轉(zhuǎn)換為相應(yīng)的輸出值。parseValue
函數(shù)將變量輸入值轉(zhuǎn)換為
相應(yīng)的PHP值剖效,parseLiteral
函數(shù)將AST文字(抽象語法樹)轉(zhuǎn)換為相應(yīng)的PHP值嫉入。
高級(jí)用法
如果您正在尋找本文檔尚未涵蓋的內(nèi)容焰盗,最好的辦法是查看本項(xiàng)目中的
測(cè)試。你會(huì)驚奇地發(fā)現(xiàn)那里有很多例子咒林。
整合
Laravel
這是一個(gè)演示如何在Laravel項(xiàng)目中使用此庫(kù)的示例熬拒。您需要一個(gè)application service
來將此庫(kù)公開給您的應(yīng)用程序,一個(gè)服務(wù)提供者來注冊(cè)該服務(wù)垫竞,一個(gè)控制器和一個(gè)
處理GraphQL POST請(qǐng)求的路由澎粟。
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);
}
}
routes/api.php
Route::post('/graphql', 'app\GraphQL\GraphQLController@handle');