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

FieldPluginBase::unwrapResult()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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