How to create a GraphQL schema with PHP and webonyx/graphql-php

Hello everyone!

Many nowadays developers are using different ways of communication between front end (client web browser) and back end (server).
One of these ways is GraphQL schema language. Using GraphQL, a developer can easily transfer data between the front end and the back end. Also you can quickly change rules or create a new one to modify structure of data. In GraphQL the rules are called – Schema. Schema consists of: Objects, Fields, Data types, etc. Also there are three types of communication with the server: Query, Mutation, Subscribe. These types describes schema of fields and types and each schema provides its principle to transfer data.

Along with this GraphQL has a different implementation in different programming languages with the same abilities. In other words, GraphQL is a schema language that is implemented in a programming language as a service. Each programming language, for instance: PHP, JavaScript, Java, Python, Ruby, etc., has a side-developed library to work with GraphQL schema. It isn’t builtin a module/library and it isn’t a part of any programming language.

That being said, GraphQL has many different implementations and below I am describing an implementation for PHP language and the library is called: webonyx/graphql-php by creating a simple Query schema. It is a quick way to create simple schema and use it to receive data from the server and then use it in your client scripts.

Being written on PHP language this library can be installed into your scripts with the Composer package manager. As described on the GitHub page of the library, you can just run the next command:

	composer require webonyx/graphql-php

Also, to parse input requests to the scripts you need to install the laminas-diactoros library by the next command:

	composer require laminas/laminas-diactoros

After finishing these commands the library will be able to use it in your project by including the file autoload.php in your script like this:

	require_once('vendor/autoload.php');

To begin using the libary and creating a simple Query scheme, it is required to define the classes of the library that will be used to process the Query Scheme:

use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Server\StandardServer;
use GraphQL\Upload\UploadMiddleware;
use Laminas\Diactoros\ServerRequestFactory;
use GraphQL\Server\ServerConfig;

Recently I ran into a problem implementing a filter for flats for one of my clients, on his real estate website, and below I will try to describe a simple way to quickly create an elementary schema for a filter like that. So, after defining the classes used for creating a schema, let’s start describing a simple object for the flats Query:

1. Creating an Object for Query schema:


$flats_item = new ObjectType([
	'name' => 'FlatItem',
	'fields' => [
				'id' => Type::int(),
                                'floor' => Type::int(),
				'num' => Type::int(),
				'price' => Type::int(),
				'area_total' => Type::int(),
				'complex' => Type::string()
	]
]);

$flats_items = new ObjectType([
	'name' => 'FlatsItems',
	'fields' => [
			'items' => Type::listof($flats_item)
	]
]);

$flats = [
	'type' => $flats_items,
	'args' => [
			'price_min' => Type::int(),
			'price_max' => Type::int(),
	],
 'resolve' => function($rootValue, $args) {
			// some code to make query answer
 }
];

The code above describes the structure of flats. Each flat has a few parameters such as id, floor (floor number), num (flat number), price, etc. The next variables($flats_items) contains a list of the flat items. Finally in the $flats variable the type of the query is defined, which is assigned by $flats_items variable and arguments that the schema can receive in the resolve function.

After defining the flats schema, you need to add into Schema Query:


$query = new ObjectType([
  'name' => 'Query',
  'fields' => [
    'flats'           => $flats,
  ]
]);

$schema = new Schema([
    'query' => $query,
]);

The assinging of the flats schema to the Schema Query is above, which means you need to create a new ObjectType in $query variable and then assign this variable to the Schema object.
Having done these several actions let’s move on to writing a code for using the Schema defined above.

2. Writing the code for processing the Query Schema


	$server_config = ServerConfig::create()
	->setSchema($schema);

	$request = ServerRequestFactory::fromGlobals();

	$rawInput = file_get_contents('php://input');

	$input = json_decode($rawInput, true);

	$request = $request->withParsedBody($input);

	$server = new StandardServer($server_config);
	$result = $server->executePsrRequest($request);
	$server->getHelper()->sendResponse($result);

In the lines above you need to create an instance of ServerConfig class and assign the Query Schema. Then, you will need to create a request instance using the class ServerRequestFactory from the laminas-diactoros library. After that, you need to pass the data from the input to the request, the data sent from the client to the server. Finally,
you will need to create a server instance, which will be using the ServerConfig instance, and a request instance to output the request’s response using method – sendResponse.

At the same time the response to your request may contain errors. But not all errors will appear in the response, only the errors about the Query Schema will. Other errors don’t add to the response. You need to do additional actions to get these errors, so if your purpose is to debug your code you need to add these lines after the server response code:

$debug = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
$output = $result->toArray($debug);
file_put_contents('/path/to/file',json_encode($output));

This code is converting all debug data to an array and saving it into the file in the JSON format.

3. Preparing the response data in the resolve function

In the code above, in the schema definition, there is a resolve function as an associated element of the array. Now to test our script that provides the response with the data of the flats on the filter request, you need to define the test data inside this function. The input arguments for the schema were price_min and price_max; these arguments also are called the fields in the request that you can send to the server. For example, let’s define a four flats in an array with the fields corresponding to our defined schema.


<?php

$flats = [
  [
          'id' => 1,
          'floor' => 2,
          'num' => 3,
          'price' => 100,
          'area_total' => 10,
          'complex' => 'Complex 1'
  ],
  [
          'id' => 2,
          'floor' => 2,
          'num' => 3,
          'price' => 200,
          'area_total' => 10,
          'complex' => 'Complex 2'
  ],
  [
          'id' => 3,
          'floor' => 2,
          'num' => 3,
          'price' => 300,
          'area_total' => 10,
          'complex' => 'Complex 3'
  ],
  [
          'id' => 3,
          'floor' => 2,
          'num' => 3,
          'price' => 400,
          'area_total' => 10,
          'complex' => 'Complex 1'
  ],
];

All in all, by writing the array of data for the flats, you need to write a simple function of filtering this data based on filter arguments – price_min or price_max. This code can be written like this:


return ['items' => array_filter($flats,function($flat) use ($args) {

				if($flat['price']>$args['price_min'])
						return true;
				else
						return false;

})];

The code uses the anonymous function in the context of PHP builtin function array_filter. The anonymous function gets each element of the flats array and the request data in the $args variable. Inside the function there is a check that is comparing the price field of each element with the filter argument – price_min and then if the price is greater than the filter argument, the flat passes to the finall array of the items for the response.

To illustrate, the query may be like this:


query flats {
  flats(price_min: 200) {
    items {
      id
      floor
      price
    }
  }
}

and the response will be like this:


{
  "data": {
    "flats": {
      "items": [
        {
          "id": 3,
          "floor": 2,
          "price": 300
        },
        {
          "id": 3,
          "floor": 2,
          "price": 400
        }
      ]
    }
  }
}

It means when you pass the variable/field in the request – price_min with the value – 200, you will receive the response only with two flats of the four because they are correspond to the filter argument and the others don’t.

Also, in the request I use only three fields – id, floor and price, but in the Query Schema there are a few more. You can easily add them into the query and receive new information in the response. For example:


query flats {
  flats(price_min: 200) {
    items {
      id
      floor
      price
      complex
    }
  }
}

After that, all flats will have a new field – complex with the information from the server that you have defined earlier.

{
  "data": {
    "flats": {
      "items": [
        {
          "id": 3,
          "floor": 2,
          "price": 300,
          "complex": "Complex 3"
        },
        {
          "id": 3,
          "floor": 2,
          "price": 400,
          "complex": "Complex 1"
        }
      ]
    }
  }
}

The code of this script you can get on the Gist.

Full review about GraphQL type system you can read on this page.