Skip to content

Instantly share code, notes, and snippets.

@titpetric
Created April 11, 2017 08:47
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save titpetric/e1414880099e0e5a90d87d56736bd87f to your computer and use it in GitHub Desktop.
Save titpetric/e1414880099e0e5a90d87d56736bd87f to your computer and use it in GitHub Desktop.
Transform GraphQL fields into JSON to use them for filtering native PHP arrays
<?php
/** A poor mans GraphQL fields parser
*
* Try to convert graphQL fields to JSON and then just use json_decode to produce an array.
*/
class Fields
{
/** Parse GraphQL fields into an array */
public static function parse($query)
{
$query = str_replace("{", "{\n", $query);
$query = str_replace("}", "\n}\n", $query);
$query = array_map("trim", explode("\n", $query));
foreach ($query as $k => $line) {
// strip comments
$line = explode("#", $line);
$line = $line[0];
// skip opening or closing tags
if ($line === "{" || $line === "") {
continue;
}
// declare as object value
if (strpos($line, "{") !== false) {
$name = trim(str_replace("{", "", $line));
$query[$k] = '"' . $name . '": {';
continue;
}
if (strpos($line, "}") !== false) {
$query[$k] .= ',';
continue;
}
$query[$k] = '"' . $line . '": true,';
}
$query = implode("", $query);
// cut last comma
$query = substr($query, 0, -1);
// cut trailing commas
$query = str_replace(",}", "}", $query);
// produce php array
$retval = json_decode($query, true);
if (is_null($retval)) {
throw new \Exception(sprintf("Error when parsing GraphQL fields: '%s'", $query));
}
return $retval;
}
/** Filter a PHP array with the GraphQL fields provided by parse() */
public static function execute($query, $data)
{
$filter = self::parse($query);
$result = array_intersect_key_recursive($data, $filter);
return $result;
}
}
function array_intersect_key_recursive($arr, $filter)
{
$is_int = true;
foreach ($arr as $k => $v) {
if (!is_int($k) || !is_array($v)) {
$is_int = false;
break;
}
}
if ($is_int) {
foreach ($arr as $k => $v) {
$arr[$k] = array_intersect_key_recursive($v, $filter);
}
return $arr;
}
$retval = array();
foreach ($filter as $key => $value) {
if (!isset($arr[$key])) {
continue;
}
if (is_array($value)) {
$retval[$key] = array_intersect_key_recursive($arr[$key], $value);
continue;
}
$retval[$key] = $arr[$key];
}
return $retval;
}
@titpetric
Copy link
Author

The above piece of code parses a very small subset of GraphQL, dealing with fields, for example:

{
    name
    id
    #comment
    value {
        name
        id
    }
}

This will be naively converted to JSON, and parsed into a PHP array:

array (
  'name' => true,
  'id' => true,
  'value' =>
  array (
    'name' => true,
    'id' => true,
  ),
)

The array_intersect_key_recursive function is the glue that allows to use the above notation to filter possible untyped/non-object API responses that you may produce with your existing API. To use it to filter your API responses might be as simple as:

if (isset($_GET['graphql_query'])) {
    $response = Fields::execute($_GET['graphql_query'], $response);
}

The code is PHP 5.3+ compatible (might work on even older PHP versions). As said, it only supports a narrow subset of GraphQL field queries, doesn't implement any kind of AST parser, no references, objects, and many other things. It's usable only as a minimal filter that will reduce your API responses to fields which you define with GraphQL.

@jorge-wheresweed
Copy link

when calling $response = Fields::execute($_GET['graphql_query'], $response); what its response?

@titpetric
Copy link
Author

titpetric commented Jan 28, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment