I'm totally new to GraphQL and wanted to play arouund with graphql-php in order to build a simple API to get started. I'm currently reading the docs and trying out the examples, but I'm stuck quite at the beginning.
I want my schema to be stored in a schema.graphql
file instead of building it manually, so I followed the docs on how to do that and it is indeed working:
<?php
// graph-ql is installed via composer
require('../vendor/autoload.php');
use GraphQL\Language\Parser;
use GraphQL\Utils\BuildSchema;
use GraphQL\Utils\AST;
use GraphQL\GraphQL;
try {
$cacheFilename = 'cached_schema.php';
// caching, as recommended in the docs, is disabled for testing
// if (!file_exists($cacheFilename)) {
$document = Parser::parse(file_get_contents('./schema.graphql'));
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true) . ';');
/*} else {
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
}*/
$typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
// In the docs, this function is just empty, but I needed to return the $typeConfig, otherwise I got an error
return $typeConfig;
};
$schema = BuildSchema::build($document, $typeConfigDecorator);
$context = (object)array();
// this has been taken from one of the examples provided in the repo
$rawInput = file_get_contents('php://input');
$input = json_decode($rawInput, true);
$query = $input['query'];
$variableValues = isset($input['variables']) ? $input['variables'] : null;
$rootValue = ['prefix' => 'You said: '];
$result = GraphQL::executeQuery($schema, $query, $rootValue, $context, $variableValues);
$output = $result->toArray();
} catch (\Exception $e) {
$output = [
'error' => [
'message' => $e->getMessage()
]
];
}
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($output);
This is what my schema.graphql
file looks like:
schema {
query: Query
}
type Query {
products: [Product!]!
}
type Product {
id: ID!,
type: ProductType
}
enum ProductType {
HDRI,
SEMISPHERICAL_HDRI,
SOUND
}
I can query it for example with
query {
__schema {types{name}}
}
and this will return the metadata as expected. But of course now I want to query for actual product data and get that from a database, and for that I'd need to define a resolver function.
The docs at http://webonyx.github.io/graphql-php/type-system/type-language/ state: "By default, such schema is created without any resolvers. We have to rely on default field resolver and root value in order to execute a query against this schema." - but there is no example for doing this.
How can I add resolver functions for each of the types/fields?
This approach works without instantiating a Server. In my case, I already have a server and can read HTTP data, all I needed was to read the GraphQL schema and run the query. First I read the schema from a file:
$schemaContent = // file_get_contents or whatever works for you
$schemaDocument = GraphQL\Language\Parser::parse($schemaContent);
$schemaBuilder = new GraphQL\Utils\BuildSchema($schemaDocument);
$schema = $schemaBuilder->buildSchema();
Then I execute the query passing a custom field resolver:
$fieldResolver = function() {
return call_user_func_array([$this, 'defaultFieldResolver'], func_get_args());
};
$result = GraphQL\GraphQL::executeQuery(
$schema,
$query, // this was grabbed from the HTTP post data
null,
$appContext, // custom context
$variables, // this was grabbed from the HTTP post data
null,
$fieldResolver // HERE, custom field resolver
);
The field resolver looks like this:
private static function defaultFieldResolver(
$source,
$args,
$context,
\GraphQL\Type\Definition\ResolveInfo $info
) {
$fieldName = $info->fieldName;
$parentType = $info->parentType->name;
if ($source === NULL) {
// this is the root value, return value depending on $fieldName
// ...
} else {
// Depending on field type ($parentType), I call different field resolvers.
// Since our system is big, we implemented a bootstrapping mechanism
// so modules can register field resolvers in this class depending on field type
// ...
// If no field resolver was defined for this $parentType,
// we just rely on the default field resolver provided by graphql-php (copy/paste).
$fieldName = $info->fieldName;
$property = null;
if (is_array($source) || $source instanceof \ArrayAccess) {
if (isset($source[$fieldName])) {
$property = $source[$fieldName];
}
} else if (is_object($source)) {
if (isset($source->{$fieldName})) {
$property = $source->{$fieldName};
}
}
return $property instanceof \Closure
? $property($source, $args, $context)
: $property;
}
}
Here's what I ended up doing...
$rootResolver = array(
'emptyCart' => function($root, $args, $context, $info) {
global $rootResolver;
initSession();
$_SESSION['CART']->clear();
return $rootResolver['getCart']($root, $args, $context, $info);
},
'addCartProduct' => function($root, $args, $context, $info) {
global $rootResolver;
...
return $rootResolver['getCart']($root, $args, $context, $info);
},
'removeCartProduct' => function($root, $args, $context, $info) {
global $rootResolver;
...
return $rootResolver['getCart']($root, $args, $context, $info);
},
'getCart' => function($root, $args, $context, $info) {
initSession();
return array(
'count' => $_SESSION['CART']->quantity(),
'total' => $_SESSION['CART']->total(),
'products' => $_SESSION['CART']->getProductData()
);
},
and then in the config
$config = ServerConfig::create()
->setSchema($schema)
->setRootValue($rootResolver)
->setContext($context)
->setDebug(DEBUG_MODE)
->setQueryBatching(true)
;
$server = new StandardServer($config);
It feels rather hack-ish to me, and I should probably outsource the resolvers into separate files, but it works... Still baffled that there are no simple examples for this task, maybe in an even better way than my solution...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With