Main goal: Write a graphQL query, it will navigate your (Rest + JSON-LD) server and do the HTTP calls for you.
This Symfony bundle lets you build a GraphQL layer to automate HTTP calls to an API Platform based server (JSON-LD + REST). It is built on top of webonyx/graphql-php.
- Command to validate graphql schema
$ bin/console graphql_on_rest:schema:validate
- Data profiler to dump GraphQL queries et HTTP requests
$ composer require fnash/graphql-on-rest-bundle
<?php
// app/AppKernel.php
use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = [
// ...
new Fnash\GraphqlOnRestBundle\FnashGraphqlOnRestBundle(),
];
// ...
}
// ...
}
Add a server "my_rest_api" and tell the bundle how you make your http requests to fetch JSON-LD data. See Configuration.php
# app/config/config.yml
fnash_graphql_on_rest:
servers:
my_rest_api:
data_provider_id: 'Acme\AppBundle\GraphQL\MyDataProvider'
<?php
namespace Acme\AppBundle\GraphQL;
use Fnash\GraphqlOnRestBundle\GraphQL\DataProvider\DataProvider;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
class MyDataProvider extends DataProvider
{
/**
* @var DecoderInterface
*/
private $serializer;
private $guzzle;
public function __construct(DecoderInterface $serializer, $guzzle)
{
$this->serializer = $serializer;
$this->guzzle = $guzzle;
}
/**
* @{inheritdoc}
*/
public function getRawData(string $url, array $queryParams = []): array
{
try {
$data = (string) $this->guzzle->get($url, $queryParams)->getBody();
return $this->serializer->decode($data, 'json');
} catch (\Exception $exception) {
return [];
}
//TODO fill example with symfony/http-client and json_decode (without serializer)
}
/**
* @param string $iri
*
* @return array
*/
public function getIri(string $iri, $context = null): array
{
// TODO: Implement getIri() method.
}
/**
* @param array $iris
*
* @return array[]
*/
public function getIris(array $iris, $context = null): array
{
// TODO: Implement getIris() method.
}
}
Make sure webonyx/graphql-php is installed
1- Define types for your queries:
<?php
namespace Acme\AppBundle\GraphQL\Type;
use Fnash\GraphqlOnRestBundle\GraphQL\Type\JsonLdObjectType;
use Fnash\GraphqlOnRestBundle\GraphQL\Type\TypeRegistry;
use Fnash\GraphqlOnRestBundle\GraphQL\TypeResolver\TypeResolver;
use GraphQL\Type\Definition\Type;
class ArticleType extends JsonLdObjectType
{
public function __construct($iriResolver)
{
$config = [
'fields' => function () use ($iriResolver) {
$fields = [
'title' => Type::string(),
'body' => Type::string(),
'related' => [
'type' => Type::listOf(TypeRegistry::get(ArticleType::class)),
'resolve' => $iriResolver,
],
'tags' => [
'type' => Type::listOf(TypeRegistry::get(TagType::class)),
'resolve' => $iriResolver,
],
];
return array_merge($fields, TypeRegistry::getInterface(ContentInterfaceType::class)->getFields());
},
'resolveField' => TypeResolver::resolveFieldClosure(),
];
parent::__construct($config);
}
}
namespace Acme\AppBundle\GraphQL\Type;
use Fnash\GraphqlOnRestBundle\GraphQL\Type\JsonLdObjectType;
use GraphQL\Type\Definition\Type;
class TagType extends JsonLdObjectType
{
public function __construct()
{
$config = [
'fields' => function () {
$fields = [
'label' => Type::string(),
];
return array_merge($fields, static::getMetaDataFields());
},
];
parent::__construct($config);
}
}
2- Create resolvers for your types to fetch data:
namespace Acme\AppBundle\GraphQL\Resolver;
use Fnash\GraphqlOnRestBundle\GraphQL\Type\TypeRegistry;
use Fnash\GraphqlOnRestBundle\GraphQL\TypeResolver\TypeResolver;
use Acme\AppBundle\GraphQL\Type\ArticleType;
use GraphQL\Type\Definition\Type;
class ArticleTypeResolver extends TypeResolver
{
/**
* {@inheritdoc}
*/
public function getQueryFieldConfig(): array
{
$type = $this->getType();
$type->resolveFieldFn = static::resolveFieldClosure();
$this->configureArguments([
'id' => Type::listOf(Type::string()),
'title',
]);
return [
'type' => Type::listOf($type),
'resolve' => $this->resolveTypeClosure($this->getUrlPath()),
'args' => $this->getArgumentsConfig(),
];
}
/**
* {@inheritdoc}
*/
public function getType(): Type
{
return TypeRegistry::get(ArticleType::class, [
$this->resolveIriDeferredClosure(),
]);
}
}
<?php
namespace Acme\AppBundle\GraphQL\Resolver;
use Fnash\GraphqlOnRestBundle\GraphQL\Type\TypeRegistry;
use Fnash\GraphqlOnRestBundle\GraphQL\TypeResolver\TypeResolver;
use Acme\AppBundle\GraphQL\Type\TagType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
class TagTypeResolver extends TypeResolver
{
/**
* {@inheritdoc}
*/
public function getQueryFieldConfig(): array
{
$type = $this->getType();
$type->resolveFieldFn = static::resolveFieldClosure();
return [
'type' => Type::listOf($type),
'resolve' => $this->resolveTypeClosure($this->getUrlPath()),
'args' => $this->getArgumentsConfig(),
];
}
/**
* {@inheritdoc}
*/
public function getType(): Type
{
return TypeRegistry::get(TagType::class);
}
}
- Resolvers must be declared as services and tagged
services:
Acme\AppBundle\GraphQL\Resolver\ArticleTypeResolver:
parent: 'graphql_on_rest.type_resolver.my_rest_api'
tags:
- { name: 'graphql_on_rest.type_resolver', server: 'my_rest_api' }
Acme\AppBundle\GraphQL\Resolver\TagTypeResolver:
parent: 'graphql_on_rest.type_resolver.my_rest_api'
tags:
- { name: 'graphql_on_rest.type_resolver', server: 'my_rest_api' }
Query 1:
{
article {
title
body
}
}
Result:
{
"data": {
"article": [
{
"title": "Nissan rappelle 2 millions de voitures dans le monde",
"body": "<p><strong>AFP - </strong>Le constructeur automobile japonais Nissan a annoncé jeudi<p>"
},
{
"title": "Vols suspects en série chez les journalistes travaillant sur l'affaire Bettencourt",
"body": "<p>Des enregistrements réalisés chez Liliane Bettencourt...</p>"
}
]
},
"extensions": {
"http_calls": [
{
"url": "/api/articles?limit=2",
"duration_ms": 355.484
}
],
"query": {
"duration_ms": 379,
"duration_no_http_ms": 6.554
}
}
}
Query 2:
{
article {
title
body
tags {
label
}
}
}
Result:
{
"data": {
"article": [
{
"title": "Nissan rappelle 2 millions de voitures dans le monde",
"body": "<p><strong>AFP - </strong>Le constructeur automobile japonais Nissan a annoncé jeudi<p>"
"tags": []
},
{
"title": "Vols suspects en série chez les journalistes travaillant sur l'affaire Bettencourt",
"body": "<p>Des enregistrements réalisés chez Liliane Bettencourt...</p>"
"tags": [
{
"label": "Justice"
},
{
"label": "France"
},
{
"label": "Affaire Bettencourt"
},
{
"label": "dépêches"
}
]
}
]
},
"extensions": {
"http_calls": [
{
"url": "/api/articles?limit=2",
"duration_ms": 340.168
},
{
"url": "/api/tags?id[0]=8f498ca0-ba33-11e7-add7-02420a050002&id[1]=8f3afcee-ba33-11e7-a697-02420a050002&id[2]=8f1eebee-ba33-11e7-b2fe-02420a050002&id[3]=8a6d60ee-ba33-11e7-9177-02420a050002&limit=4",
"duration_ms": 91.786
}
],
"query": {
"duration_ms": 470,
"duration_no_http_ms": 18.419
}
}
}
- update webonyx version