Completed
Pull Request — 8.x-3.x (#440)
by Sebastian
01:55
created

SchemaLoader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 15
nc 1
nop 6
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0
1
<?php
2
3
namespace Drupal\graphql\GraphQL\Schema;
4
5
use Drupal\graphql\GraphQL\Utility\TypeCollector;
6
use Drupal\Core\Cache\Cache;
7
use Drupal\Core\Cache\CacheableDependencyInterface;
8
use Drupal\Core\Cache\CacheableMetadata;
9
use Drupal\Core\Cache\CacheBackendInterface;
10
use Drupal\Core\Cache\Context\CacheContextsManager;
11
use Drupal\graphql\GraphQL\Validator\TypeValidationRule;
12
use Drupal\graphql\Plugin\GraphQL\SchemaPluginInterface;
13
use Drupal\graphql\Plugin\GraphQL\SchemaPluginManager;
14
use Symfony\Component\HttpFoundation\RequestStack;
15
use Youshido\GraphQL\Schema\AbstractSchema;
16
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
17
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
18
use Youshido\GraphQL\Type\Object\AbstractObjectType;
19
use Youshido\GraphQL\Validator\ConfigValidator\ConfigValidator;
20
21
/**
22
 * Loads and caches a generated GraphQL schema.
23
 */
24
class SchemaLoader {
25
  /**
26
   * The cache contexts manager service.
27
   *
28
   * @var \Drupal\Core\Cache\Context\CacheContextsManager
29
   */
30
  protected $contextsManager;
31
32
  /**
33
   * The schema plugin manager service.
34
   *
35
   * @var \Drupal\graphql\Plugin\GraphQL\SchemaPluginManager
36
   */
37
  protected $schemaManager;
38
39
  /**
40
   * The schema cache backend.
41
   *
42
   * @var \Drupal\Core\Cache\CacheBackendInterface
43
   */
44
  protected $schemaCache;
45
46
  /**
47
   * The cache metadata cache.
48
   *
49
   * @var \Drupal\Core\Cache\CacheBackendInterface
50
   */
51
  protected $metadataCache;
52
53
  /**
54
   * The service configuration.
55
   *
56
   * @var array
57
   */
58
  protected $config;
59
60
  /**
61
   * The request stack service.
62
   *
63
   * @var \Symfony\Component\HttpFoundation\RequestStack
64
   */
65
  protected $requestStack;
66
67
  /**
68
   * Static cache of loaded schemas.
69
   *
70
   * @var \Drupal\graphql\Plugin\GraphQL\SchemaPluginInterface[]
71
   */
72
  protected $schemas = [];
73
74
  /**
75
   * Static cache of loaded cache metadata.
76
   *
77
   * @var \Drupal\Core\Cache\CacheableDependencyInterface[]
78
   */
79
  protected $metadata = [];
80
81
  /**
82
   * Constructs a SchemaLoader object.
83
   *
84
   * @param \Drupal\Core\Cache\Context\CacheContextsManager $contextsManager
85
   *   The cache contexts manager service.
86
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
87
   *   The request stack service.
88
   * @param \Drupal\graphql\Plugin\GraphQL\SchemaPluginManager $schemaManager
89
   *   The schema plugin manager service.
90
   * @param \Drupal\Core\Cache\CacheBackendInterface $schemaCache
91
   *   The schema cache backend.
92
   * @param \Drupal\Core\Cache\CacheBackendInterface $metadataCache
93
   *   The metadata cache backend.
94
   * @param array $config
95
   *   The configuration provided through the services.yml.
96
   */
97
  public function __construct(
98
    CacheContextsManager $contextsManager,
99
    RequestStack $requestStack,
100
    SchemaPluginManager $schemaManager,
101
    CacheBackendInterface $schemaCache,
102
    CacheBackendInterface $metadataCache,
103
    array $config
104
  ) {
105
    $this->config = $config;
106
107
    // Override the default type validator to enable services as field resolver
108
    // callbacks.
109
    $validator = ConfigValidator::getInstance();
110
    $validator->addRule('type', new TypeValidationRule($validator));
111
112
    $this->schemaManager = $schemaManager;
113
    $this->contextsManager = $contextsManager;
114
    $this->schemaCache = $schemaCache;
115
    $this->metadataCache = $metadataCache;
116
    $this->requestStack = $requestStack;
117
  }
118
119
  /**
120
   * Loads and caches the generated schema.
121
   *
122
   * @param string $name
123
   *   The name of the schema to load.
124
   *
125
   * @return \Drupal\graphql\Plugin\GraphQL\SchemaPluginInterface
126
   *   The generated GraphQL schema.
127
   */
128
  public function getSchema($name) {
129
    if (array_key_exists($name, $this->schemas)) {
130
      return $this->schemas[$name];
131
    }
132
133
    // The cache key is made up of all of the globally known cache contexts.
134
    if (!empty($this->config['schema_cache'])) {
135
      if (($contextCache = $this->metadataCache->get("$name:schema")) && $contextCache->data) {
136
        $cid = $this->getCacheIdentifier($name, $contextCache->data);
137
138
        if (($schema = $this->schemaCache->get($cid)) && $schema->data) {
139
          return $this->schemas[$name] = $schema->data;
140
        }
141
      }
142
    }
143
144
    $this->schemas[$name] = $this->schemaManager->createInstance($name);
145
    // If the schema is not cacheable, just return it directly.
146
    if (empty($this->config['schema_cache'])) {
147
      return $this->schemas[$name];
148
    }
149
150
    // Compute the cache identifier, tag and expiry time.
151
    $schemaCacheMetadata = $this->getSchemaCacheMetadata($name);
152
    if ($schemaCacheMetadata->getCacheMaxAge() !== 0) {
153
      $tags = $schemaCacheMetadata->getCacheTags();
154
      $expire = $this->maxAgeToExpire($schemaCacheMetadata->getCacheMaxAge());
155
      $cid = $this->getCacheIdentifier($name, $schemaCacheMetadata);
156
157
      // Write the cache entry for the schema cache entries.
158
      $this->schemaCache->set($cid, $this->schemas[$name], $expire, $tags);
159
    }
160
161
    return $this->schemas[$name];
162
  }
163
164
  /**
165
   * Retrieves the schema's cache metadata.
166
   *
167
   * @param string $name
168
   *   The name of the schema.
169
   * @return \Drupal\Core\Cache\CacheableDependencyInterface
170
   *   The cache metadata for the schema.
171
   */
172
  public function getSchemaCacheMetadata($name) {
173
    return $this->getCacheMetadata($name, "$name:schema", function (SchemaPluginInterface $schema) {
174
      return $schema->getSchemaCacheMetadata();
175
    });
176
  }
177
178
  /**
179
   * Retrieves the schema's response cache metadata.
180
   *
181
   * @param string $name
182
   *   The name of the schema.
183
   * @return \Drupal\Core\Cache\CacheableDependencyInterface
184
   *   The cache metadata for the schema's responses.
185
   */
186
  public function getResponseCacheMetadata($name) {
187
    return $this->getCacheMetadata($name, "$name:response", function (SchemaPluginInterface $schema) {
188
      return $schema->getResponseCacheMetadata();
189
    });
190
  }
191
192
  /**
193
   * Helper function to load cache metadata from a schema.
194
   *
195
   * @param string $name
196
   *   The name of the schema.
197
   * @param string $cid
198
   *   The cache identifier for caching the metadata
199
   * @param callable $callback
200
   *   Callback to return the cache metadata from the schema.
201
   *
202
   * @return \Drupal\Core\Cache\CacheableDependencyInterface
203
   *   The cache metadata.
204
   */
205
  protected function getCacheMetadata($name, $cid, callable $callback) {
206
    if (array_key_exists($cid, $this->metadata)) {
207
      return $this->metadata[$cid];
208
    }
209
210
    // The cache key is made up of all of the globally known cache contexts.
211
    if (!empty($this->config['schema_cache'])) {
212
      if (($metadataCache = $this->metadataCache->get($cid)) && $metadataCache->data) {
213
        return $this->metadata[$name] = $metadataCache->data;
214
      }
215
    }
216
217
    /** @var \Drupal\Core\Cache\CacheableDependencyInterface $metadata */
218
    $schema = $this->getSchema($name);
219
    $metadata = $callback($schema);
220
    $this->metadata[$cid] = $metadata;
221
    if (empty($this->config['schema_cache'])) {
222
      return $this->metadata[$cid];
223
    }
224
225
    // Use the schema cache metadata to determine cache expiry and tags.
226
    $schemaCacheMetadata = $schema->getSchemaCacheMetadata();
227
    if ($schemaCacheMetadata->getCacheMaxAge() !== 0) {
228
      $tags = $schemaCacheMetadata->getCacheTags();
229
      $expire = $this->maxAgeToExpire($schemaCacheMetadata->getCacheMaxAge());
230
231
      // Write the cache entry for the response cache metadata.
232
      $this->metadataCache->set($cid, $metadata, $expire, $tags);
233
    }
234
235
    return $metadata;
236
  }
237
238
  /**
239
   * Maps a max age value to an "expire" value for the Cache API.
240
   *
241
   * @param int $maxAge
242
   *   A max age value.
243
   *
244
   * @return int
245
   *   A corresponding "expire" value.
246
   *
247
   * @see \Drupal\Core\Cache\CacheBackendInterface::set()
248
   */
249
  protected function maxAgeToExpire($maxAge) {
250
    return ($maxAge === Cache::PERMANENT) ? Cache::PERMANENT : (int) $this->requestStack->getMasterRequest()->server->get('REQUEST_TIME') + $maxAge;
251
  }
252
253
  /**
254
   * Generates a cache identifier for the passed cache contexts.
255
   *
256
   * @param string $name
257
   *   The name of the schema.
258
   * @param \Drupal\Core\Cache\CacheableDependencyInterface $metadata
259
   *   Optional array of cache context tokens.
260
   *
261
   * @return string The generated cache identifier.
262
   *   The generated cache identifier.
263
   */
264
  protected function getCacheIdentifier($name, CacheableDependencyInterface $metadata) {
265
    $tokens = $metadata->getCacheContexts();
266
    $keys = $this->contextsManager->convertTokensToKeys($tokens)->getKeys();
267
    return implode(':', array_merge(['graphql', $name], array_values($keys)));
268
  }
269
270
}
271