Completed
Pull Request — 8.x-3.x (#588)
by Sebastian
02:05
created

FieldPluginBase::isLanguageAwareField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Drupal\graphql\Plugin\GraphQL\Fields;
4
5
use Drupal\Component\Plugin\PluginBase;
6
use Drupal\Component\Render\MarkupInterface;
7
use Drupal\Core\Cache\CacheableDependencyInterface;
8
use Drupal\Core\Cache\CacheableMetadata;
9
use Drupal\Core\Render\RenderContext;
10
use Drupal\graphql\GraphQL\Execution\ResolveContext;
11
use Drupal\graphql\GraphQL\ValueWrapperInterface;
12
use Drupal\graphql\Plugin\FieldPluginInterface;
13
use Drupal\graphql\Plugin\FieldPluginManager;
14
use Drupal\graphql\Plugin\GraphQL\Traits\ArgumentAwarePluginTrait;
15
use Drupal\graphql\Plugin\GraphQL\Traits\CacheablePluginTrait;
16
use Drupal\graphql\Plugin\GraphQL\Traits\DeprecatablePluginTrait;
17
use Drupal\graphql\Plugin\GraphQL\Traits\DescribablePluginTrait;
18
use Drupal\graphql\Plugin\GraphQL\Traits\TypedPluginTrait;
19
use Drupal\graphql\Plugin\LanguageNegotiation\LanguageNegotiationGraphQL;
20
use Drupal\graphql\Plugin\SchemaBuilderInterface;
21
use GraphQL\Deferred;
22
use GraphQL\Type\Definition\ListOfType;
23
use GraphQL\Type\Definition\NonNull;
24
use GraphQL\Type\Definition\ResolveInfo;
25
26
abstract class FieldPluginBase extends PluginBase implements FieldPluginInterface {
27
  use CacheablePluginTrait;
28
  use DescribablePluginTrait;
29
  use TypedPluginTrait;
30
  use ArgumentAwarePluginTrait;
31
  use DeprecatablePluginTrait;
32
33
  /**
34
   * The language context service.
35
   *
36
   * @var \Drupal\graphql\GraphQLLanguageContext
37
   */
38
  protected $languageContext;
39
40
  /**
41
   * The renderer service.
42
   *
43
   * @var \Drupal\Core\Render\RendererInterface
44
   */
45
  protected $renderer;
46
47
  /**
48
   * {@inheritdoc}
49
   */
50
  public static function createInstance(SchemaBuilderInterface $builder, FieldPluginManager $manager, $definition, $id) {
51
    return [
52
      'description' => $definition['description'],
53
      'contexts' => $definition['contexts'],
54
      'deprecationReason' => $definition['deprecationReason'],
55
      'type' => $builder->processType($definition['type']),
56
      'args' => $builder->processArguments($definition['args']),
57
      'resolve' => function ($value, array $args, ResolveContext $context, ResolveInfo $info) use ($manager, $id) {
58
        $instance = $manager->getInstance(['id' => $id]);
59
        return $instance->resolve($value, $args, $context, $info);
60
      },
61
    ];
62
  }
63
64
  /**
65
   * Get the language context instance.
66
   *
67
   * @return \Drupal\graphql\GraphQLLanguageContext
68
   *   The language context service.
69
   */
70
  protected function getLanguageContext() {
71
    if (!isset($this->languageContext)) {
72
      $this->languageContext = \Drupal::service('graphql.language_context');
73
    }
74
    return $this->languageContext;
75
  }
76
77
  /**
78
   * Get the renderer service.
79
   *
80
   * @return \Drupal\Core\Render\RendererInterface
81
   */
82
  protected function getRenderer() {
83
    if (!isset($this->renderer)) {
84
      $this->renderer = \Drupal::service('renderer');
85
    }
86
    return $this->renderer;
87
  }
88
89
  /**
90
   * {@inheritdoc}
91
   */
92
  public function getDefinition() {
93
    $definition = $this->getPluginDefinition();
94
95
    return [
96
      'type' => $this->buildType($definition),
97
      'description' => $this->buildDescription($definition),
98
      'args' => $this->buildArguments($definition),
99
      'deprecationReason' => $this->buildDeprecationReason($definition),
100
      'contexts' => $this->buildCacheContexts($definition),
101
    ];
102
  }
103
104
  /**
105
   * {@inheritdoc}
106
   */
107
  public function resolve($value, array $args, ResolveContext $context, ResolveInfo $info) {
108
    $definition = $this->getPluginDefinition();
109
110
    // If not resolving in a trusted environment, check if the field is secure.
111
    if (!$context->getGlobal('development', FALSE) && !$context->getGlobal('bypass field security', FALSE)) {
112
      if (empty($definition['secure'])) {
113
        throw new \Exception(sprintf("Unable to resolve insecure field '%s'.", $info->fieldName));
114
      }
115
    }
116
117
    foreach ($definition['contextual_arguments'] as $argument) {
118
      if (array_key_exists($argument, $args) && !is_null($args[$argument])) {
119
        $context->setContext($argument, $args[$argument], $info);
120
      }
121
      else {
122
        $args[$argument] = $context->getContext($argument, $info);
123
      }
124
    }
125
126
    $renderContext = new RenderContext();
127
    $result = $this->getRenderer()->executeInRenderContext($renderContext, function () use ($value, $args, $context, $info) {
128
      if ($this->isLanguageAwareField()) {
129
        return $this->getLanguageContext()->executeInLanguageContext(function () use ($value, $args, $context, $info) {
130
          return $this->resolveDeferred([$this, 'resolveValues'], $value, $args, $context, $info);
131
        }, $context->getContext('language', $info));
132
      }
133
134
      return $this->resolveDeferred([$this, 'resolveValues'], $value, $args, $context, $info);
135
    }
136
                                                      
137
    // Collect leaked render cache metadata if this is a query.
138
    if ($info->operation->operation === 'query') {
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_IF, expecting ',' or ')'
Loading history...
139
      if (!$renderContext->isEmpty()) {
140
        $context->addCacheableDependency($renderContext->pop());
141
      }
142
    }
143
  }
144
145
  /**
146
   * Indicator if the field is language aware.
147
   *
148
   * Checks for 'languages:*' cache contexts on the fields definition.
149
   *
150
   * @return bool
151
   *   The fields language awareness status.
152
   */
153
  protected function isLanguageAwareField() {
154
    return (boolean) count(array_filter($this->getPluginDefinition()['response_cache_contexts'], function ($context) {
155
      return strpos($context, 'languages:') === 0;
156
    }));
157
  }
158
159
  /**
160
   * {@inheritdoc}
161
   */
162
  protected function resolveDeferred(callable $callback, $value, array $args, ResolveContext $context, ResolveInfo $info) {    
163
    $result = $callback($value, $args, $context, $info);
164
    if (is_callable($result)) {
165
      return new Deferred(function () use ($result, $value, $args, $context, $info) {
166
        return $this->resolveDeferred($result, $value, $args, $context, $info);
167
      });
168
    }
169
170
    $result = iterator_to_array($result);
171
    // Collect cache metadata from the result if this is a query.
172
    if ($info->operation->operation === 'query') {
173
      $dependencies = $this->getCacheDependencies($result, $value, $args, $context, $info);
174
      foreach ($dependencies as $dependency) {
175
        $context->addCacheableDependency($dependency);
176
      }
177
    }
178
179
    return $this->unwrapResult($result, $info);
180
  }
181
182
  /**
183
   * Unwrap the resolved values.
184
   *
185
   * @param array $result
186
   *   The resolved values.
187
   * @param \GraphQL\Type\Definition\ResolveInfo $info
188
   *   The resolve info object.
189
   *
190
   * @return mixed
191
   *   The extracted values (an array of values in case this is a list, an
192
   *   arbitrary value if it isn't).
193
   */
194
  protected function unwrapResult($result, ResolveInfo $info) {
195
    $result = array_map(function ($item) {
196
      return $item instanceof ValueWrapperInterface ? $item->getValue() : $item;
197
    }, $result);
198
199
    $result = array_map(function ($item) {
200
      return $item instanceof MarkupInterface ? $item->__toString() : $item;
201
    }, $result);
202
203
    // If this is a list, return the result as an array.
204
    $type = $info->returnType;
205
    if ($type instanceof ListOfType || ($type instanceof NonNull && $type->getWrappedType() instanceof ListOfType)) {
206
      return $result;
207
    }
208
209
    return !empty($result) ? reset($result) : NULL;
210
  }
211
212
  /**
213
   * Retrieve the list of cache dependencies for a given value and arguments.
214
   *
215
   * @param array $result
216
   *   The result of the field.
217
   * @param mixed $parent
218
   *   The parent value.
219
   * @param array $args
220
   *   The arguments passed to the field.
221
   * @param \Drupal\graphql\GraphQL\Execution\ResolveContext $context
222
   *   The resolve context.
223
   * @param \GraphQL\Type\Definition\ResolveInfo $info
224
   *   The resolve info object.
225
   *
226
   * @return array
227
   *   A list of cacheable dependencies.
228
   */
229
  protected function getCacheDependencies(array $result, $parent, array $args, ResolveContext $context, ResolveInfo $info) {
230
    $self = new CacheableMetadata();
231
    $definition = $this->getPluginDefinition();
232
    if (!empty($definition['response_cache_contexts'])) {
233
      $self->addCacheContexts($definition['response_cache_contexts']);
234
    }
235
236
    if (!empty($definition['response_cache_tags'])) {
237
      $self->addCacheTags($definition['response_cache_tags']);
238
    }
239
240
    if (isset($definition['response_cache_max_age'])) {
241
      $self->mergeCacheMaxAge($definition['response_cache_max_age']);
242
    }
243
244
    return array_merge([$self], array_filter($result, function ($item) {
245
      return $item instanceof CacheableDependencyInterface;
246
    }));
247
  }
248
249
  /**
250
   * Retrieve the list of field values.
251
   *
252
   * Always returns a list of field values. Even for single value fields.
253
   * Single/multi field handling is responsibility of the base class.
254
   *
255
   * @param mixed $value
256
   *   The current object value.
257
   * @param array $args
258
   *   Field arguments.
259
   * @param $context
260
   *   The resolve context.
261
   * @param \GraphQL\Type\Definition\ResolveInfo $info
262
   *   The resolve info object.
263
   *
264
   * @return \Generator
265
   *   The value generator.
266
   */
267
  protected function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
268
    // Allow overriding this class without having to declare this method.
269
    yield NULL;
270
  }
271
272
}
273