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; |
|
|
|
|
66
|
|
|
}); |
67
|
|
|
if ($operations) { |
|
|
|
|
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) { |
|
|
|
|
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]; |
|
|
|
|
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) { |
|
|
|
|
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
|
|
|
|
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 thecomposer.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
orrequire-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 you have not tested against this specific condition, such errors might go unnoticed.