Completed
Push — 8.x-1.x ( 944e24...3fc52f )
by Philipp
01:41
created

graphql_twig.module::graphql_twig_theme_registry_alter()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 4
nop 1
dl 0
loc 14
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @file
5
 * GraphQL Twig hook implementations.
6
 */
7
8
use GraphQL\Language\AST\DefinitionNode;
9
use GraphQL\Language\AST\InputValueDefinitionNode;
10
use GraphQL\Language\AST\OperationDefinitionNode;
11
use GraphQL\Language\Parser;
12
use GraphQL\Language\Source;
13
use GraphQL\Server\OperationParams;
14
use Drupal\Core\Entity\EntityInterface;
15
use Drupal\graphql_twig\GraphQLTwigEnvironment;
16
use Drupal\Core\Site\Settings;
17
18
/**
19
 * Implements hook_theme_registry_alter().
20
 *
21
 * Search for GraphQL enhanced templates in the theme registry and append
22
 * the GraphQL preprocessor.
23
 */
24
function graphql_twig_theme_registry_alter(&$theme_registry) {
25
  foreach ($theme_registry as $hook => $info) {
26
    if (!(isset($info['path']) && isset($info['template']))) {
27
      continue;
28
    }
29
30
    $file = $info['path'] . '/' . $info['template'] . '.html.twig';
31
    if ($query = graphql_twig_get_query($file)) {
32
      $theme_registry[$hook]['graphql_enabled'] = TRUE;
33
      $theme_registry[$hook]['graphql_variables'] = graphql_twig_get_query_variables($query);
34
      $theme_registry[$hook]['preprocess functions'][] = 'graphql_twig_process_query';
35
    }
36
  }
37
}
38
39
/**
40
 * Get the query string for a given template file.
41
 *
42
 * Checks for an annotation, a `*.gql` sibling file or returns NULL if nothing
43
 * is found.
44
 *
45
 * @param string $file
46
 *   The template file path.
47
 *
48
 * @return string|null
49
 *   The GraphQL query string or NULL.
50
 */
51
function graphql_twig_get_query($file) {
52
  if (file_exists($file)) {
53
54
    $graphqlFile = $file . '.gql';
55
    if (file_exists($graphqlFile)) {
56
      return file_get_contents($graphqlFile);
57
    }
58
59
    preg_match(GraphQLTwigEnvironment::$GRAPHQL_ANNOTATION_REGEX, file_get_contents($file), $matches);
60
    if (array_key_exists('query', $matches)) {
61
      $parser = new Parser(new Source($matches['query']));
62
      $doc = $parser->parseDocument();
63
      if ($doc->definitions) {
64
        $operations = array_filter(iterator_to_array($doc->definitions->getIterator()), function (DefinitionNode $node) {
65
          return $node instanceof OperationDefinitionNode;
0 ignored issues
show
Bug introduced by
The class GraphQL\Language\AST\OperationDefinitionNode does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
66
        });
67
        if ($operations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $operations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
68
          return $matches['query'];
69
        }
70
      }
71
    }
72
  }
73
74
  return NULL;
75
}
76
77
/**
78
 * Get the declared variables in a GraphQL query string.
79
 *
80
 * @param $query
81
 *   The query string.
82
 *
83
 * @return array
84
 *   A list of variable names.
85
 */
86
function graphql_twig_get_query_variables($query) {
87
  $parser = new Parser(new Source($query));
88
  return array_map(function (InputValueDefinitionNode $var) {
89
    return $var->name->value;
90
  }, $parser->parseArgumentDefs());
91
}
92
93
/**
94
 * Implements hook_preprocess().
95
 *
96
 * Execute the GraphQL query if the theme hook is GraphQL enhanced and add
97
 * the query result as well as cache metadata.
98
 */
99
function graphql_twig_process_query(&$variables, $hook, $info) {
0 ignored issues
show
Unused Code introduced by
The parameter $hook is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
100
  if (isset($info['graphql_enabled']) && $info['graphql_enabled']) {
101
    /** @var \Drupal\graphql\GraphQL\Execution\QueryProcessor $processor */
102
    $processor = Drupal::service('graphql.query_processor');
103
104
    /** @var \Drupal\Core\Template\TwigEnvironment $twig */
105
    $twig = Drupal::service('twig');
106
107
    $file = $info['path'] . '/' . $info['template'] . '.html.twig';
108
109
    $query = $twig->loadTemplate($file)->getGraphQLQuery();
110
111
    $arguments = [];
112
    foreach ($info['graphql_variables'] as $var) {
113
      if (isset($variables[$var])) {
114
        $arguments[$var] = $variables[$var] instanceof EntityInterface ? $variables[$var]->id() : $variables[$var];
0 ignored issues
show
Bug introduced by
The class Drupal\Core\Entity\EntityInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
115
      }
116
    }
117
118
    $queryResult = $processor->processQuery('default:default', OperationParams::create([
119
      'query' => $query,
120
      'variables' => $arguments,
121
    ]));
122
123
    $debug = Settings::get('graphql_twig_debug', FALSE);
124
125
    $variables['graphql'] = [
126
      'data' => $queryResult->data,
127
      'errors' => $queryResult->errors,
128
    ];
129
    $variables['graphql']['query'] = $query;
130
    $variables['graphql']['variables'] = json_encode($arguments);
131
    $variables['graphql']['debug'] = $debug;
132
133
    $variables['#cache']['contexts'] = $queryResult->getCacheContexts();
134
    $variables['#cache']['tags'] = $queryResult->getCacheTags();
135
    $variables['#cache']['max-age'] = $queryResult->getCacheMaxAge();
136
137
    if ($debug) {
138
      $variables['#attached']['library'][] = 'graphql_twig/debug';;
139
    }
140
  }
141
}
142
143
/**
144
 * Implements hook_theme().
145
 *
146
 * Add theme entries for any GraphQL annotated template files.
147
 */
148
function graphql_twig_theme($existing, $type, $theme, $path) {
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $theme is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $path is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
149
  $themeRegistry = [];
150
  /** @var \Drupal\Core\Theme\ThemeManagerInterface $themeManager */
151
  $themeManager = \Drupal::service('theme.manager');
152
153
  $activeTheme = $themeManager->getActiveTheme();
154
  $paths = [$activeTheme->getName() => $activeTheme->getPath()];
155
156
  $paths += array_map(function (\Drupal\Core\Theme\ActiveTheme $theme) {
157
    return $theme->getPath();
158
  }, $activeTheme->getBaseThemes());
159
160
  foreach (array_reverse($paths) as $path) {
161
    foreach (file_scan_directory($path . '/templates', '/.*\.html\.twig/') as $file) {
162
      $template = substr($file->filename,0, strlen('.html.twig') * -1);
163
164
      // Do not add template suggestions.
165
      if (strstr($template, '--')) {
166
        continue;
167
      }
168
169
      $hook = str_replace('-', '_', $template);
170
171
      if (array_key_exists($hook, $existing)) {
172
        continue;
173
      }
174
175
      if ($query = graphql_twig_get_query($file->uri)) {
176
        $themeRegistry[$hook] = [
177
          'template' => $template,
178
          'path' => dirname($file->uri),
179
          'theme path' => $path,
180
          'variables' => [],
181
        ];
182
        foreach (graphql_twig_get_query_variables($query) as $var) {
183
          $themeRegistry[$hook]['variables'][$var] = NULL;
184
        }
185
      }
186
    }
187
  }
188
189
  return $themeRegistry;
190
}
191
192
/**
193
 * Implements hook_module_implements_alter().
194
 *
195
 * Make sure graphql_twig_theme runs last to not accidentially override existing
196
 * theme hooks.
197
 */
198
function graphql_twig_module_implements_alter(&$implementations, $hook) {
199
200
  if ($hook == 'theme') {
201
    unset($implementations['graphql_twig']);
202
    $implementations['graphql_twig'] = FALSE;
203
  }
204
205
}
206