Completed
Pull Request — 8.x-3.x (#588)
by Sebastian
03:20 queued 01:10
created

FieldPluginBase::resolveDeferred()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 3
nop 5
dl 0
loc 19
rs 9.2
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 View Code Duplication
  public function getDefinition() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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') {
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;
0 ignored issues
show
Bug introduced by
The class Drupal\Component\Render\MarkupInterface 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...
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;
0 ignored issues
show
Bug introduced by
The class Drupal\Core\Cache\CacheableDependencyInterface 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...
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