Graphql-php

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í)例,您可以使用提供的FileSourceBuilderMultiFileSourceBuilder類瞬矩。

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)行Mutationssubscriptions皇型。

$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ò)展AbstractTypeResolverAbstractFieldResolver來定義自己的解析器购岗√或者自行實(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ù)可以返回“值”烹困,promisepromise數(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');

文章來源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市欢瞪,隨后出現(xiàn)的幾起案子活烙,更是在濱河造成了極大的恐慌,老刑警劉巖遣鼓,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啸盏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡骑祟,警方通過查閱死者的電腦和手機(jī)回懦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來次企,“玉大人粉怕,你說我怎么就攤上這事∈愠玻” “怎么了贫贝?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蛉谜。 經(jīng)常有香客問我稚晚,道長(zhǎng),這世上最難降的妖魔是什么型诚? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任客燕,我火速辦了婚禮,結(jié)果婚禮上狰贯,老公的妹妹穿的比我還像新娘也搓。我一直安慰自己,他們只是感情好涵紊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布傍妒。 她就那樣靜靜地躺著,像睡著了一般摸柄。 火紅的嫁衣襯著肌膚如雪颤练。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天驱负,我揣著相機(jī)與錄音嗦玖,去河邊找鬼患雇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宇挫,可吹牛的內(nèi)容都是我干的苛吱。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼器瘪,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼翠储!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起娱局,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤彰亥,失蹤者是張志新(化名)和其女友劉穎咧七,沒想到半個(gè)月后衰齐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡继阻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年耻涛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘟檩。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抹缕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墨辛,到底是詐尸還是另有隱情卓研,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布睹簇,位于F島的核電站奏赘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏太惠。R本人自食惡果不足惜磨淌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凿渊。 院中可真熱鬧梁只,春花似錦、人聲如沸埃脏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彩掐。三九已至淤翔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佩谷,已是汗流浹背旁壮。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工监嗜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抡谐。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓裁奇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親麦撵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刽肠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容