Skip to content

Commit 8b362e0

Browse files
authored
Contextual language handling. (#550)
* Intermediate commit, not functional. * Don't abort context retrieval on falsy path elements. * Removed all implicit language contexts. * Contextual language negotiation. * Enforcing graphql language negotiation through config overrides. * Fixing language negotiator. * Fixed negotiation. * Ignore language cache contexts. * Added upgrade instructions for languages. * Docs fix. * Fix to also work without language module enabled. * Fixed wrong return, has to be yield. * Added core 8.5 tests. * Docs fix. * Moved contextual language to service. * Travis debug. * Removed travis debug. * Renamed test class according to file. * More tests and resulting fixes. * Simplified language context. * Explicit callable execution. * More fine grained tests. * Test negotiator initialization result. * Test cleanup. * Test disable negotiator fix. * Debugging ... * Debugging ... * Debugging ... * Debugging ... * Check negotiator instance. * Changed module initialization order. * Depending on language module. * Revert "Depending on language module." This reverts commit f8df099 * Revert "Changed module initialization order." This reverts commit bbaa77f * Proper service provider classname. * Properly injecting the language context. * Annotation style fixes. * Revert "Properly injecting the language context." This reverts commit c358b53 * Real setter injection. * Moved property to top. * Use language cache contexts to conditionally set the graphql language context. * Enabled multilingual features for all tests to catch potential context leaks. * Ignore language contexts when creating the cache key. * Reproducing leaking translation context. * Add language_content cache context to RouteEntity since the access handler emits it for some reason. * Fixed exception expectation.
1 parent 962268c commit 8b362e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+895
-155
lines changed

doc/upgrade/beta6.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22
## Schema changes
33
These changes affect you if you are using the schema automatically generated by the `graphql_core` module.
44

5+
6+
### Language handling
7+
8+
Multilingual queries changed drastically. The endpoints language negotiation is ignored entirely, instead all language handling is left to the query and its arguments. A couple of fields accept a "language" argument, and whenever this argument is filled explicitly, it's value will be inherited to subsequent occurrences. The `route` field will set this context implicitly from the paths language prefix.
9+
10+
```graphql
11+
query {
12+
route(path: "/node/1") {
13+
... on EntityCanonicalUrl {
14+
entity {
15+
# Will emit the default language.
16+
entityLabel
17+
}
18+
}
19+
}
20+
route(path: "/fr/node/1") {
21+
... on EntityCanonicalUrl {
22+
entity {
23+
# Will emit the french translation.
24+
entityLabel
25+
}
26+
}
27+
}
28+
}
29+
30+
```
31+
532
### Url Interfaces
633
The type structure of the `Url` object changed. While before there have been just the `InternalUrl` and `ExternalUrl` types, the `InternalUrl` has become an interface that can resolve to different Url types, depending on the underlying route. The `DefaultInternalUrl` has the fields for context resolving and other generic rout information. The `EntityCanonicalUrl` has access to the underlying entity.
734

graphql.services.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,19 @@ services:
244244

245245
# Buffers.
246246
graphql.buffer.entity:
247-
class: Drupal\graphql\GraphQL\Buffers\EntityBuffer
248-
arguments: ['@entity_type.manager']
247+
class: Drupal\graphql\GraphQL\Buffers\EntityBuffer
248+
arguments: ['@entity_type.manager']
249249
graphql.buffer.subrequest:
250-
class: Drupal\graphql\GraphQL\Buffers\SubRequestBuffer
251-
arguments: ['@http_kernel', '@request_stack']
250+
class: Drupal\graphql\GraphQL\Buffers\SubRequestBuffer
251+
arguments: ['@http_kernel', '@request_stack']
252+
253+
graphql.language_context:
254+
class: Drupal\graphql\GraphQLLanguageContext
255+
arguments: ['@language_manager']
256+
257+
graphql.config_factory_override:
258+
class: Drupal\graphql\Config\GraphQLConfigOverrides
259+
arguments: ['@config.storage']
260+
tags:
261+
- { name: config.factory.override, priority: -253 }
262+

modules/graphql_core/src/Plugin/Deriver/Interfaces/EntityTypeDeriver.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ public function getDerivativeDefinitions($basePluginDefinition) {
3939
$derivative['response_cache_contexts'][] = 'user.node_grants:view';
4040
}
4141

42-
if ($type->isTranslatable()) {
43-
$derivative['response_cache_contexts'][] = 'languages:language_content';
44-
}
45-
4642
$this->derivatives[$typeId] = $derivative;
4743
}
4844

modules/graphql_core/src/Plugin/Deriver/Types/EntityBundleDeriver.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,6 @@ public function getDerivativeDefinitions($basePluginDefinition) {
9090
$derivative['response_cache_contexts'][] = 'user.node_grants:view';
9191
}
9292

93-
if ($type->isTranslatable()) {
94-
$derivative['response_cache_contexts'][] = 'languages:language_content';
95-
}
96-
9793
$this->derivatives[$typeId . '-' . $bundle] = $derivative;
9894
}
9995
}

modules/graphql_core/src/Plugin/Deriver/Types/EntityTypeDeriver.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ public function getDerivativeDefinitions($basePluginDefinition) {
4040
$derivative['response_cache_contexts'][] = 'user.node_grants:view';
4141
}
4242

43-
if ($type->isTranslatable()) {
44-
$derivative['response_cache_contexts'][] = 'languages:language_content';
45-
}
46-
4743
$this->derivatives[$typeId] = $derivative;
4844
}
4945

modules/graphql_core/src/Plugin/GraphQL/Fields/Entity/EntityById.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Drupal\Core\Entity\EntityRepositoryInterface;
77
use Drupal\Core\Entity\EntityTypeManagerInterface;
88
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
9+
use Drupal\Core\TypedData\TranslatableInterface;
910
use Drupal\graphql\GraphQL\Buffers\EntityBuffer;
1011
use Drupal\graphql\GraphQL\Cache\CacheableValue;
1112
use Drupal\graphql\GraphQL\Execution\ResolveContext;
@@ -20,6 +21,7 @@
2021
* arguments = {
2122
* "id" = "String!"
2223
* },
24+
* contextual_arguments = {"language"},
2325
* deriver = "Drupal\graphql_core\Plugin\Deriver\Fields\EntityByIdDeriver"
2426
* )
2527
*/
@@ -109,8 +111,8 @@ protected function resolveValues($value, array $args, ResolveContext $context, R
109111
$access = $entity->access('view', NULL, TRUE);
110112

111113
if ($access->isAllowed()) {
112-
if (isset($args['language']) && $args['language'] != $entity->language()->getId()) {
113-
$entity = $this->entityRepository->getTranslationFromContext($entity, $args['language']);
114+
if (isset($args['language']) && $args['language'] != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
115+
$entity = $entity->getTranslation($args['language']);
114116
}
115117

116118
yield $entity->addCacheableDependency($access);

modules/graphql_core/src/Plugin/GraphQL/Fields/Entity/EntityRevisionById.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Drupal\Core\Entity\EntityRepositoryInterface;
88
use Drupal\Core\Entity\EntityTypeManagerInterface;
99
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
10+
use Drupal\Core\TypedData\TranslatableInterface;
1011
use Drupal\graphql\GraphQL\Cache\CacheableValue;
1112
use Drupal\graphql\GraphQL\Execution\ResolveContext;
1213
use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase;
@@ -20,6 +21,7 @@
2021
* arguments = {
2122
* "id" = "String!"
2223
* },
24+
* contextual_arguments = {"language"},
2325
* deriver = "Drupal\graphql_core\Plugin\Deriver\Fields\EntityRevisionByIdDeriver"
2426
* )
2527
*/
@@ -98,8 +100,8 @@ protected function resolveValues($value, array $args, ResolveContext $context, R
98100
}
99101
/** @var \Drupal\Core\Access\AccessResultInterface $access */
100102
else if (($access = $entity->access('view', NULL, TRUE)) && $access->isAllowed()) {
101-
if (isset($args['language']) && $args['language'] != $entity->language()->getId()) {
102-
$entity = $this->entityRepository->getTranslationFromContext($entity, $args['language']);
103+
if ($entity instanceof TranslatableInterface && isset($args['language']) && $args['language'] != $entity->language()->getId()) {
104+
$entity = $entity->getTranslation($args['language']);
103105
}
104106

105107
yield new CacheableValue($entity, [$access]);

modules/graphql_core/src/Plugin/GraphQL/Fields/Entity/EntityTranslation.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function __construct(array $configuration, $pluginId, $pluginDefinition,
6868
*/
6969
public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
7070
if ($value instanceof EntityInterface && $value instanceof TranslatableInterface && $value->isTranslatable()) {
71-
yield $this->entityRepository->getTranslationFromContext($value, $args['language']);
71+
yield $value->getTranslation($args['language']);
7272
}
7373
}
7474

modules/graphql_core/src/Plugin/GraphQL/Fields/Entity/EntityTranslations.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public function resolveValues($value, array $args, ResolveContext $context, Reso
7272
if ($value instanceof ContentEntityInterface && $value instanceof TranslatableInterface && $value->isTranslatable()) {
7373
$languages = $value->getTranslationLanguages();
7474
foreach ($languages as $language) {
75-
yield $this->entityRepository->getTranslationFromContext($value, $language->getId());
75+
yield $value->getTranslation($language->getId());
7676
}
7777
}
7878
}

modules/graphql_core/src/Plugin/GraphQL/Fields/EntityFieldBase.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace Drupal\graphql_core\Plugin\GraphQL\Fields;
44

55
use Drupal\Component\Render\MarkupInterface;
6+
use Drupal\Core\Entity\ContentEntityInterface;
7+
use Drupal\Core\Entity\Entity;
8+
use Drupal\Core\Entity\EntityInterface;
69
use Drupal\Core\Field\FieldItemInterface;
710
use Drupal\graphql\GraphQL\Execution\ResolveContext;
811
use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase;
@@ -31,6 +34,10 @@ protected function resolveItem($item, array $args, ResolveContext $context, Reso
3134
$result = $type->serialize($result);
3235
}
3336

37+
if ($result instanceof ContentEntityInterface && $result->isTranslatable() && $language = $context->getContext('language', $info)) {
38+
$result = $result->getTranslation($language);
39+
}
40+
3441
return $result;
3542
}
3643
}

0 commit comments

Comments
 (0)