getting started in graphql-php: how to add resolver functions to schema from .graphql file?

getting started in graphql-php: how to add resolver functions to schema from .graphql file?


5

I’m totally new to GraphQL and wanted to play around 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 GraphQLLanguageParser;
use GraphQLUtilsBuildSchema;
use GraphQLUtilsAST;
use GraphQLGraphQL;

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, "<?phpnreturn " . 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 https://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?

5

  • Have you found any answer to this by your own? Care to share here if so? Thanks!

    – Seb

    Aug 22, 2018 at 18:53

  • Hi @Seb, I posted an answer below.

    – Constantin Groß

    Aug 24, 2018 at 8:14

  • I found a different way, it feels rather hackish too but it works without creating a Server and will add it as answer for posterity (?)

    – Seb

    Aug 27, 2018 at 14:24

  • This has become more important to my app, now that there are graphql webpack loaders coming out. I'm duplicating a lot of effort defining my schema as a bunch of big PHP arrays, then building my queries client-side from big strings. I could have .graphql files that are loaded in buildSchema server-side, and import in the client, thus making sure the definitions stay in sync.

    – Josh from Qaribou

    Nov 2, 2018 at 18:07

  • I found the siler lib has a nice way to load graphql (load the schema, then load the resolvers), but it's simply on top of graphql-php. You can check out their code to see how it's done — looks like they use GraphQLExecutorExecutor a lot to set up the resolvers. github.com/leocavalcante/siler/blob/master/src/Graphql/…

    – Josh from Qaribou

    Nov 2, 2018 at 18:11

4 Answers
4


2

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 = GraphQLLanguageParser::parse($schemaContent);
        $schemaBuilder = new GraphQLUtilsBuildSchema($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 = GraphQLGraphQL::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,
    GraphQLTypeDefinitionResolveInfo $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;
    }
}


1

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…


0

I’m using root value for this:

<?php

require("vendor/autoload.php") ;
require("exemplo-graphql.php");
require("Usuario.php");

use GraphQLGraphQL;
use GraphQLTypeSchema;
use GraphQLUtilsBuildSchema;

$query = $_REQUEST['query'];

$typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
    $name = $typeConfig['name'];
    // ... add missing options to $typeConfig based on type $name
    return $typeConfig;
};

$contents = file_get_contents('schema.graphql');
$schema = BuildSchema::build($contents, $typeConfigDecorator);

// $rawInput = file_get_contents('php://input');
$input = json_decode($query, true);
$query = $input['query'];
$variableValues = isset($input['variables']) ? $input['variables'] : null;

try {
    // $rootValue = ['prefix' => 'You said: '];
    $rootValue = [
        'usuario' => function($root, $args, $context, $info) {
            $usuario = new Usuario();
            $usuario->setNome("aqui tem um teste");
            $usuario->setEmail("aqui tem um email");
            return $usuario;
        },
        'echo' => function($root, $args, $context, $info) {
            return "aqui tem um echooo";
        },
        'adicionarUsuario' => function ($root, $args, $context, $info) {
            $usuario = new Usuario();
            $usuario->setNome("aqui tem um teste");
            $usuario->setEmail("aqui tem um email");
            return $usuario;
        }
    ];

    $result = GraphQL::executeQuery($schema, $query, $rootValue, null,
        $variableValues);

    if ($result->errors) {
        $output = [
            'errors' => [
                [
                    'message' => $result->errors
                ]
            ]
    ];
    } else {
        $output = $result->toArray();
    }
} catch (Exception $e) {
    $output = [
        'errors' => [
            [
                'message' => $e->getMessage()
            ]
        ]
    ];
} 

header('Content-Type: application/json');
echo json_encode($output);


0

By default, schema which was created by using BuildSchema::build() was created without any resolvers. So we need to define our custom resolvers as follows:

$contents = file_get_contents($this->projectDir.'/config/schema.graphql');
$typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
    $name = $typeConfig['name'];
    if ($name === 'Query') {
       $typeConfig['resolveField'] =
           function ($source, $args, $context, ResolveInfo $info) {
               if ($info->fieldDefinition->name == 'login') {
                   if ($args['userName'] === 'test' && $args['password'] === '1234') {
                       return "Valid User.";
                   } else {
                       return "Invalid User";
                   }
               } elseif ($info->fieldDefinition->name == 'validateUser') {
                   if ($args['age'] < 18) {
                       return ['userId' => $args['userId'], 'category' => 'Not eligible for voting'];
                    } 
                    }
                }
                }
            ;
        }
        return $typeConfig;
    };
$schema = BuildSchema::build($contents, $typeConfigDecorator);

The above example I have added resolvers for my two queries namely ‘login’ and ‘validateUser.’

No need to define any root values and defaultFieldResolver. Our custom resolvers are enough.



Leave a Reply

Your email address will not be published. Required fields are marked *