FieldPluginBase   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 269
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 36
eloc 94
c 5
b 1
f 0
dl 0
loc 269
rs 9.52

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getRenderer() 0 5 2
A createInstance() 0 10 1
A isLanguageAwareField() 0 7 2
B resolveDeferred() 0 51 9
A getCacheDependencies() 0 17 4
A resolveValues() 0 3 1
A getLanguageContext() 0 5 2
A getDefinition() 0 9 1
B resolve() 0 18 7
B unwrapResult() 0 16 7
1
<?php
2
3
namespace Drupal\graphql\Plugin\GraphQL\Fields;
4
5
use Drupal\Component\Plugin\PluginBase;
0 ignored issues
show
Bug introduced by
The type Drupal\Component\Plugin\PluginBase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Drupal\Component\Render\MarkupInterface;
0 ignored issues
show
Bug introduced by
The type Drupal\Component\Render\MarkupInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Drupal\Core\Cache\CacheableDependencyInterface;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Cache\CacheableDependencyInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Drupal\Core\Cache\CacheableMetadata;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Cache\CacheableMetadata was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Drupal\Core\Render\RenderContext;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Render\RenderContext was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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\SchemaBuilderInterface;
20
use GraphQL\Deferred;
21
use GraphQL\Type\Definition\ListOfType;
22
use GraphQL\Type\Definition\NonNull;
23
use GraphQL\Type\Definition\ResolveInfo;
24
25
abstract class FieldPluginBase extends PluginBase implements FieldPluginInterface {
26
  use CacheablePluginTrait;
27
  use DescribablePluginTrait;
28
  use TypedPluginTrait;
29
  use ArgumentAwarePluginTrait;
30
  use DeprecatablePluginTrait;
31
32
  /**
33
   * The language context service.
34
   *
35
   * @var \Drupal\graphql\GraphQLLanguageContext
36
   */
37
  protected $languageContext;
38
39
  /**
40
   * The renderer service.
41
   *
42
   * @var \Drupal\Core\Render\RendererInterface
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Render\RendererInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
43
   */
44
  protected $renderer;
45
46
  /**
47
   * Static cache for `isLanguageAwareField()`
48
   *
49
   * @var boolean
50
   */
51
  protected $isLanguageAware = NULL;
52
53
  /**
54
   * {@inheritdoc}
55
   */
56
  public static function createInstance(SchemaBuilderInterface $builder, FieldPluginManager $manager, $definition, $id) {
57
    return [
58
      'description' => $definition['description'],
59
      'contexts' => $definition['contexts'],
60
      'deprecationReason' => $definition['deprecationReason'],
61
      'type' => $builder->processType($definition['type']),
62
      'args' => $builder->processArguments($definition['args']),
63
      'resolve' => function ($value, array $args, ResolveContext $context, ResolveInfo $info) use ($manager, $id) {
64
        $instance = $manager->getInstance(['id' => $id]);
65
        return $instance->resolve($value, $args, $context, $info);
66
      },
67
    ];
68
  }
69
70
  /**
71
   * Get the language context instance.
72
   *
73
   * @return \Drupal\graphql\GraphQLLanguageContext
74
   *   The language context service.
75
   */
76
  protected function getLanguageContext() {
77
    if (!isset($this->languageContext)) {
78
      $this->languageContext = \Drupal::service('graphql.language_context');
79
    }
80
    return $this->languageContext;
81
  }
82
83
  /**
84
   * Get the renderer service.
85
   *
86
   * @return \Drupal\Core\Render\RendererInterface
87
   */
88
  protected function getRenderer() {
89
    if (!isset($this->renderer)) {
90
      $this->renderer = \Drupal::service('renderer');
91
    }
92
    return $this->renderer;
93
  }
94
95
  /**
96
   * {@inheritdoc}
97
   */
98
  public function getDefinition() {
99
    $definition = $this->getPluginDefinition();
100
101
    return [
102
      'type' => $this->buildType($definition),
103
      'description' => $this->buildDescription($definition),
104
      'args' => $this->buildArguments($definition),
105
      'deprecationReason' => $this->buildDeprecationReason($definition),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->buildDeprecationReason($definition) targeting Drupal\graphql\Plugin\Gr...uildDeprecationReason() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
106
      'contexts' => $this->buildCacheContexts($definition),
107
    ];
108
  }
109
110
  /**
111
   * {@inheritdoc}
112
   */
113
  public function resolve($value, array $args, ResolveContext $context, ResolveInfo $info) {
114
    $definition = $this->getPluginDefinition();
115
116
    // If not resolving in a trusted environment, check if the field is secure.
117
    if (!$context->getGlobal('development', FALSE) && !$context->getGlobal('bypass field security', FALSE)) {
118
      if (empty($definition['secure'])) {
119
        throw new \Exception(sprintf("Unable to resolve insecure field '%s'.", $info->fieldName));
120
      }
121
    }
122
123
    foreach ($definition['contextual_arguments'] as $argument) {
124
      if (array_key_exists($argument, $args) && !is_null($args[$argument])) {
125
        $context->setContext($argument, $args[$argument], $info);
126
      }
127
      $args[$argument] = $context->getContext($argument, $info);
128
    }
129
130
    return $this->resolveDeferred([$this, 'resolveValues'], $value, $args, $context, $info);
131
  }
132
133
  /**
134
   * Indicator if the field is language aware.
135
   *
136
   * Checks for 'languages:*' cache contexts on the fields definition.
137
   *
138
   * @return bool
139
   *   The fields language awareness status.
140
   */
141
  protected function isLanguageAwareField() {
142
    if (is_null($this->isLanguageAware)) {
0 ignored issues
show
introduced by
The condition is_null($this->isLanguageAware) is always false.
Loading history...
143
      $this->isLanguageAware = (boolean) count(array_filter($this->getPluginDefinition()['response_cache_contexts'], function ($context) {
144
        return strpos($context, 'languages:') === 0;
145
      }));
146
    }
147
    return $this->isLanguageAware;
148
  }
149
150
  /**
151
   * {@inheritdoc}
152
   */
153
  protected function resolveDeferred(callable $callback, $value, array $args, ResolveContext $context, ResolveInfo $info) {
154
    $isLanguageAware = $this->isLanguageAwareField();
155
    $languageContext = $this->getLanguageContext();
156
157
    $renderContext = new RenderContext();
158
159
    $executor = function () use ($callback, $renderContext, $value, $args, $context, $info) {
160
      return $this->getRenderer()->executeInRenderContext($renderContext, function () use ($callback, $value, $args, $context, $info) {
161
        $result = $callback($value, $args, $context, $info);
162
        if ($result instanceof \Generator) {
163
          $result = iterator_to_array($result);
164
        }
165
        return $result;
166
      });
167
    };
168
169
    $result = $isLanguageAware
170
      ? $languageContext->executeInLanguageContext($executor, $context->getContext('language', $info))
171
      : $executor();
172
173
    if (!$renderContext->isEmpty() && $info->operation->operation === 'query') {
174
      $context->addCacheableDependency($renderContext->pop());
175
    }
176
177
    if (is_callable($result)) {
178
      return new Deferred(
179
        function () use ($result, $value, $args, $context, $info, $isLanguageAware, $languageContext) {
180
          if ($isLanguageAware) {
181
            return $languageContext
182
              ->executeInLanguageContext(
183
                function () use ($result, $value, $args, $context, $info) {
184
                  return $this->resolveDeferred($result, $value, $args, $context, $info);
185
                },
186
                $context->getContext('language', $info)
187
              );
188
          }
189
          return $this->resolveDeferred($result, $value, $args, $context, $info);
190
        }
191
      );
192
    }
193
194
    // Only collect cache metadata if this is a query. All other operation types
195
    // are not cacheable anyways.
196
    if ($info->operation->operation === 'query') {
197
      $dependencies = $this->getCacheDependencies($result, $value, $args, $context, $info);
198
      foreach ($dependencies as $dependency) {
199
        $context->addCacheableDependency($dependency);
200
      }
201
    }
202
203
    return $this->unwrapResult($result, $info);
204
  }
205
206
  /**
207
   * Unwrap the resolved values.
208
   *
209
   * @param array $result
210
   *   The resolved values.
211
   * @param \GraphQL\Type\Definition\ResolveInfo $info
212
   *   The resolve info object.
213
   *
214
   * @return mixed
215
   *   The extracted values (an array of values in case this is a list, an
216
   *   arbitrary value if it isn't).
217
   */
218
  protected function unwrapResult($result, ResolveInfo $info) {
219
    $result = array_map(function ($item) {
220
      return $item instanceof ValueWrapperInterface ? $item->getValue() : $item;
221
    }, $result);
222
223
    $result = array_map(function ($item) {
224
      return $item instanceof MarkupInterface ? $item->__toString() : $item;
225
    }, $result);
226
227
    // If this is a list, return the result as an array.
228
    $type = $info->returnType;
229
    if ($type instanceof ListOfType || ($type instanceof NonNull && $type->getWrappedType() instanceof ListOfType)) {
230
      return $result;
231
    }
232
233
    return !empty($result) ? reset($result) : NULL;
234
  }
235
236
  /**
237
   * Retrieve the list of cache dependencies for a given value and arguments.
238
   *
239
   * @param array $result
240
   *   The result of the field.
241
   * @param mixed $parent
242
   *   The parent value.
243
   * @param array $args
244
   *   The arguments passed to the field.
245
   * @param \Drupal\graphql\GraphQL\Execution\ResolveContext $context
246
   *   The resolve context.
247
   * @param \GraphQL\Type\Definition\ResolveInfo $info
248
   *   The resolve info object.
249
   *
250
   * @return array
251
   *   A list of cacheable dependencies.
252
   */
253
  protected function getCacheDependencies(array $result, $parent, array $args, ResolveContext $context, ResolveInfo $info) {
254
    $self = new CacheableMetadata();
255
    $definition = $this->getPluginDefinition();
256
    if (!empty($definition['response_cache_contexts'])) {
257
      $self->addCacheContexts($definition['response_cache_contexts']);
258
    }
259
260
    if (!empty($definition['response_cache_tags'])) {
261
      $self->addCacheTags($definition['response_cache_tags']);
262
    }
263
264
    if (isset($definition['response_cache_max_age'])) {
265
      $self->mergeCacheMaxAge($definition['response_cache_max_age']);
266
    }
267
268
    return array_merge([$self], array_filter($result, function ($item) {
269
      return $item instanceof CacheableDependencyInterface;
270
    }));
271
  }
272
273
  /**
274
   * Retrieve the list of field values.
275
   *
276
   * Always returns a list of field values. Even for single value fields.
277
   * Single/multi field handling is responsibility of the base class.
278
   *
279
   * @param mixed $value
280
   *   The current object value.
281
   * @param array $args
282
   *   Field arguments.
283
   * @param $context
284
   *   The resolve context.
285
   * @param \GraphQL\Type\Definition\ResolveInfo $info
286
   *   The resolve info object.
287
   *
288
   * @return \Generator
289
   *   The value generator.
290
   */
291
  protected function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
292
    // Allow overriding this class without having to declare this method.
293
    yield NULL;
294
  }
295
296
}
297