1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Drupal\graphql\Plugin\GraphQL; |
4
|
|
|
|
5
|
|
|
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; |
6
|
|
|
use Drupal\Component\Plugin\PluginManagerInterface; |
7
|
|
|
|
8
|
|
|
class PluggableSchemaBuilder { |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Static cache of type system plugin instances. |
12
|
|
|
* |
13
|
|
|
* @var \Drupal\graphql\Plugin\GraphQL\TypeSystemPluginInterface |
14
|
|
|
*/ |
15
|
|
|
protected $instances = []; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Static cache of plugin definitions. |
19
|
|
|
* |
20
|
|
|
* @var array |
21
|
|
|
*/ |
22
|
|
|
protected $definitions; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* PluggableSchemaBuilder constructor. |
26
|
|
|
* |
27
|
|
|
* @param array $definitions |
28
|
|
|
* List of plugin definitions. |
29
|
|
|
*/ |
30
|
|
|
public function __construct(array $definitions) { |
31
|
|
|
$this->definitions = $definitions; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Search for a specific plugin. |
36
|
|
|
* |
37
|
|
|
* @param callable $selector |
38
|
|
|
* A selector callable that will be used to array_filter the list of |
39
|
|
|
* plugin definitions. |
40
|
|
|
* @param integer[] $types |
41
|
|
|
* A list of type constants. |
42
|
|
|
* @param bool $invert |
43
|
|
|
* Invert the selector result. |
44
|
|
|
* |
45
|
|
|
* @return \Drupal\graphql\Plugin\GraphQL\TypeSystemPluginInterface[] |
46
|
|
|
* The list of matching plugin instances, keyed by name. |
47
|
|
|
* |
48
|
|
|
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException |
49
|
|
|
*/ |
50
|
|
|
public function find(callable $selector, array $types, $invert = FALSE) { |
51
|
|
|
$instances = []; |
52
|
|
|
foreach ($this->definitions as $index => $definition) { |
53
|
|
|
$name = $definition['definition']['name']; |
54
|
|
|
if (empty($name)) { |
55
|
|
|
throw new InvalidPluginDefinitionException('Invalid GraphQL plugin definition. No name defined.'); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
if (!array_key_exists($name, $instances) && in_array($definition['definition']['pluginType'], $types)) { |
59
|
|
|
if ((($invert && !$selector($definition['definition'])) || $selector($definition['definition']))) { |
60
|
|
|
$instances[$name] = $this->getInstance($definition['manager'], $definition['type'], $definition['id']); |
61
|
|
|
} |
62
|
|
|
} |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
return $instances; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Search for a specific plugin. |
70
|
|
|
* |
71
|
|
|
* @param string $name |
72
|
|
|
* The specific plugin name. |
73
|
|
|
* @param integer[] $types |
74
|
|
|
* A list of type constants. |
75
|
|
|
* |
76
|
|
|
* @return \Drupal\graphql\Plugin\GraphQL\TypeSystemPluginInterface |
77
|
|
|
* The highest weighted plugin with a specific name. |
78
|
|
|
* |
79
|
|
|
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException |
80
|
|
|
*/ |
81
|
|
|
public function findByName($name, array $types) { |
82
|
|
|
$result = $this->find(function($definition) use ($name) { |
83
|
|
|
return $definition['name'] === $name; |
84
|
|
|
}, $types); |
85
|
|
|
|
86
|
|
|
if (empty($result)) { |
87
|
|
|
throw new InvalidPluginDefinitionException(sprintf('GraphQL plugin with name %s could not be found.', $name)); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
return array_pop($result); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Find the matching GraphQL data type for a Drupal type data identifier. |
95
|
|
|
* |
96
|
|
|
* Respects type chains. `entity:node:article` should return the |
97
|
|
|
* `NodeArticle` type if it is exposed or fall back to either `Node` or even |
98
|
|
|
* `Entity` otherwise. |
99
|
|
|
* |
100
|
|
|
* @param string $dataType |
101
|
|
|
* The typed data identifier. E.g. `string` or `entity:node:article`. |
102
|
|
|
* @param string[] $types |
103
|
|
|
* A list of type constants. |
104
|
|
|
* |
105
|
|
|
* @return \Drupal\graphql\Plugin\GraphQL\TypeSystemPluginInterface |
106
|
|
|
* The matching type with the highest weight. |
107
|
|
|
*/ |
108
|
|
|
public function findByDataType($dataType, array $types = [ |
109
|
|
|
GRAPHQL_UNION_TYPE_PLUGIN, |
110
|
|
|
GRAPHQL_TYPE_PLUGIN, |
111
|
|
|
GRAPHQL_INTERFACE_PLUGIN, |
112
|
|
|
GRAPHQL_SCALAR_PLUGIN, |
113
|
|
|
]) { |
114
|
|
|
$chain = explode(':', $dataType); |
115
|
|
|
|
116
|
|
|
while ($chain) { |
|
|
|
|
117
|
|
|
$dataType = implode(':', $chain); |
118
|
|
|
|
119
|
|
|
$types = $this->find(function($definition) use ($dataType) { |
120
|
|
|
return isset($definition['data_type']) && $definition['data_type'] == $dataType; |
121
|
|
|
}, $types); |
|
|
|
|
122
|
|
|
|
123
|
|
|
if (!empty($types)) { |
124
|
|
|
return array_pop($types); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
array_pop($chain); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
return NULL; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Retrieve all mutations. |
135
|
|
|
* |
136
|
|
|
* @return \Drupal\graphql\Plugin\GraphQL\TypeSystemPluginInterface[] |
137
|
|
|
* The list of mutation plugins. |
138
|
|
|
*/ |
139
|
|
|
public function getMutations() { |
140
|
|
|
return $this->find(function() { |
141
|
|
|
return TRUE; |
142
|
|
|
}, [GRAPHQL_MUTATION_PLUGIN]); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Retrieve all fields that are not associated with a specific type. |
147
|
|
|
* |
148
|
|
|
* @return \Drupal\graphql\Plugin\GraphQL\TypeSystemPluginInterface[] |
149
|
|
|
* The list root field plugins. |
150
|
|
|
*/ |
151
|
|
|
public function getRootFields() { |
152
|
|
|
// Retrieve the list of fields that are explicitly attached to a type. |
153
|
|
|
$attachedFields = array_reduce(array_filter(array_map(function($definition) { |
154
|
|
|
return array_key_exists('fields', $definition['definition']) ? $definition['definition']['fields'] : NULL; |
155
|
|
|
}, $this->definitions)), 'array_merge', []); |
156
|
|
|
|
157
|
|
|
// Retrieve the list of fields that are not attached in any way or |
158
|
|
|
// explicitly attached to the artificial "Root" type. |
159
|
|
|
return $this->find(function($definition) use ($attachedFields) { |
160
|
|
|
return (!in_array($definition['name'], $attachedFields) && empty($definition['parents'])) || in_array('Root', $definition['parents']); |
161
|
|
|
}, [GRAPHQL_FIELD_PLUGIN]); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Creates a type system plugin instance for a given plugin manager. |
166
|
|
|
* |
167
|
|
|
* @param \Drupal\Component\Plugin\PluginManagerInterface $manager |
168
|
|
|
* The plugin manager responsible for creation of the plugin instance. |
169
|
|
|
* @param $pluginType |
170
|
|
|
* The plugin type. |
171
|
|
|
* @param $pluginId |
172
|
|
|
* The plugin id. |
173
|
|
|
* |
174
|
|
|
* @return \Drupal\graphql\Plugin\GraphQL\TypeSystemPluginInterface |
175
|
|
|
* The created plugin instance. |
176
|
|
|
*/ |
177
|
|
|
protected function getInstance(PluginManagerInterface $manager, $pluginType, $pluginId) { |
178
|
|
|
if (!isset($this->instances[$pluginType][$pluginId])) { |
179
|
|
|
// Initialize the static cache array if necessary. |
180
|
|
|
$this->instances[$pluginType] = isset($this->instances[$pluginType]) ? $this->instances[$pluginType] : []; |
181
|
|
|
|
182
|
|
|
// We do not allow plugin configuration for now. |
183
|
|
|
$instance = $manager->createInstance($pluginId); |
184
|
|
|
if (empty($instance)) { |
185
|
|
|
throw new \LogicException(sprintf('Could not instantiate plugin %s of type %s.', $pluginId, $pluginType)); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
if (!$instance instanceof TypeSystemPluginInterface) { |
189
|
|
|
throw new \LogicException(sprintf('Plugin %s of type %s does not implement \Drupal\graphql\Plugin\GraphQL\TypeSystemPluginInterface.', $pluginId, $pluginType)); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
// Prevent circular dependencies by building the type after constructing the plugin instance. |
193
|
|
|
$this->instances[$pluginType][$pluginId] = $instance; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
return $this->instances[$pluginType][$pluginId]; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* {@inheritdoc} |
201
|
|
|
*/ |
202
|
|
|
public function __sleep() { |
203
|
|
|
// Don't write the plugin instances into the cache. |
204
|
|
|
return array_keys(array_diff_key(get_object_vars($this), ['instances' => TRUE])); |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
|
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.