Skip to content

Commit 79d09dd

Browse files
authored
fix(dataproducer): Populate context default values before resolving (#1403)
1 parent d9ad85d commit 79d09dd

File tree

7 files changed

+160
-8
lines changed

7 files changed

+160
-8
lines changed

config/install/graphql.settings.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dataproducer_populate_default_values: true

config/schema/graphql.schema.yml

+10
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,13 @@ graphql.default_persisted_query_configuration:
6666

6767
plugin.plugin_configuration.persisted_query.*:
6868
type: graphql.default_persisted_query_configuration
69+
70+
graphql.settings:
71+
type: config_object
72+
label: "GraphQL Settings"
73+
mapping:
74+
# @todo Remove in GraphQL 5.
75+
dataproducer_populate_default_values:
76+
type: boolean
77+
label: "Populate dataproducer context default values"
78+
description: "Legacy setting: Populate dataproducer context default values before executing the resolve method. Set this to true to be future-proof. This setting is deprecated and will be removed in a future release."

graphql.install

+12
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,15 @@ function graphql_update_8001(): void {
6969
*/
7070
function graphql_update_8400() :void {
7171
}
72+
73+
/**
74+
* Preserve dataproducer default value behavior for old installations.
75+
*
76+
* Set dataproducer_populate_default_values to TRUE after you verified that your
77+
* dataproducers are still working with the new default value behavior.
78+
*/
79+
function graphql_update_10400() :void {
80+
\Drupal::configFactory()->getEditable('graphql.settings')
81+
->set('dataproducer_populate_default_values', FALSE)
82+
->save();
83+
}

src/Plugin/DataProducerPluginManager.php

+22
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ class DataProducerPluginManager extends DefaultPluginManager {
3535
*/
3636
protected $resultCacheBackend;
3737

38+
/**
39+
* Backwards compatibility flag to populate context defaults or not.
40+
*
41+
* @todo Remove in 5.x.
42+
*/
43+
protected bool $populateContextDefaults = TRUE;
44+
3845
/**
3946
* DataProducerPluginManager constructor.
4047
*
@@ -83,6 +90,21 @@ public function __construct(
8390
$this->requestStack = $requestStack;
8491
$this->contextsManager = $contextsManager;
8592
$this->resultCacheBackend = $resultCacheBackend;
93+
94+
// We don't use dependency injection here to avoid a constructor signature
95+
// change.
96+
// @phpcs:disable
97+
// @phpstan-ignore-next-line
98+
$this->populateContextDefaults = \Drupal::config('graphql.settings')->get('dataproducer_populate_default_values') ?? TRUE;
99+
// @phpcs:enable
100+
}
101+
102+
/**
103+
* {@inheritdoc}
104+
*/
105+
public function createInstance($plugin_id, array $configuration = []) {
106+
$configuration['dataproducer_populate_default_values'] = $this->populateContextDefaults;
107+
return parent::createInstance($plugin_id, $configuration);
86108
}
87109

88110
/**

src/Plugin/GraphQL/DataProducer/DataProducerPluginBase.php

+17-2
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,27 @@ public function resolveField(FieldContext $field) {
5050
if (!method_exists($this, 'resolve')) {
5151
throw new \LogicException('Missing data producer resolve method.');
5252
}
53-
54-
$context = $this->getContextValues();
53+
$populateDefaultValues = $this->configuration['dataproducer_populate_default_values'] ?? TRUE;
54+
$context = $populateDefaultValues ? $this->getContextValuesWithDefaults() : $this->getContextValues();
5555
return call_user_func_array(
5656
[$this, 'resolve'],
5757
array_values(array_merge($context, [$field]))
5858
);
5959
}
6060

61+
/**
62+
* Initializes all contexts and populates default values.
63+
*
64+
* We cannot use ::getContextValues() here because it does not work with
65+
* default_value.
66+
*/
67+
public function getContextValuesWithDefaults(): array {
68+
$values = [];
69+
foreach ($this->getContextDefinitions() as $name => $definition) {
70+
$values[$name] = $this->getContext($name)->getContextValue();
71+
}
72+
73+
return $values;
74+
}
75+
6176
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace Drupal\Tests\graphql\Kernel\DataProducer;
4+
5+
use Drupal\Core\Session\AccountInterface;
6+
use Drupal\graphql\GraphQL\Execution\FieldContext;
7+
use Drupal\graphql\Plugin\GraphQL\DataProducer\Entity\EntityLoad;
8+
use Drupal\Tests\graphql\Kernel\GraphQLTestBase;
9+
use GraphQL\Deferred;
10+
use PHPUnit\Framework\Assert;
11+
12+
/**
13+
* Context default value test.
14+
*
15+
* @group graphql
16+
*/
17+
class DefaultValueTest extends GraphQLTestBase {
18+
19+
/**
20+
* Test that the entity_load data producer has the correct default values.
21+
*/
22+
public function testEntityLoadDefaultValue(): void {
23+
$manager = $this->container->get('plugin.manager.graphql.data_producer');
24+
$plugin = $manager->createInstance('entity_load');
25+
// Only type is required.
26+
$plugin->setContextValue('type', 'node');
27+
$context_values = $plugin->getContextValuesWithDefaults();
28+
$this->assertTrue($context_values['access']);
29+
$this->assertSame('view', $context_values['access_operation']);
30+
}
31+
32+
/**
33+
* Test that the legacy dataproducer_populate_default_values setting works.
34+
*
35+
* @dataProvider settingsProvider
36+
*/
37+
public function testLegacyDefaultValueSetting(bool $populate_setting, string $testClass): void {
38+
$this->container->get('config.factory')->getEditable('graphql.settings')
39+
->set('dataproducer_populate_default_values', $populate_setting)
40+
->save();
41+
$manager = $this->container->get('plugin.manager.graphql.data_producer');
42+
43+
// Manipulate the plugin definitions to use our test class for entity_load.
44+
$definitions = $manager->getDefinitions();
45+
$definitions['entity_load']['class'] = $testClass;
46+
$reflection = new \ReflectionClass($manager);
47+
$property = $reflection->getProperty('definitions');
48+
$property->setAccessible(TRUE);
49+
$property->setValue($manager, $definitions);
50+
51+
$this->executeDataProducer('entity_load', ['type' => 'node']);
52+
}
53+
54+
/**
55+
* Data provider for the testLegacyDefaultValueSetting test.
56+
*/
57+
public function settingsProvider(): array {
58+
return [
59+
[FALSE, TestLegacyEntityLoad::class],
60+
[TRUE, TestNewEntityLoad::class],
61+
];
62+
}
63+
64+
}
65+
66+
/**
67+
* Helper class to test the legacy behavior.
68+
*/
69+
class TestLegacyEntityLoad extends EntityLoad {
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function resolve($type, $id, ?string $language, ?array $bundles, ?bool $access, ?AccountInterface $accessUser, ?string $accessOperation, FieldContext $context): ?Deferred {
75+
// Old behavior: no default values applied, so we get NULL here.
76+
Assert::assertNull($access);
77+
Assert::assertNull($accessOperation);
78+
return NULL;
79+
}
80+
81+
}
82+
83+
/**
84+
* Helper class to test the new behavior.
85+
*/
86+
class TestNewEntityLoad extends EntityLoad {
87+
88+
/**
89+
* {@inheritdoc}
90+
*/
91+
public function resolve($type, $id, ?string $language, ?array $bundles, ?bool $access, ?AccountInterface $accessUser, ?string $accessOperation, FieldContext $context): ?Deferred {
92+
// New behavior: default values are applied.
93+
Assert::assertTrue($access);
94+
Assert::assertSame('view', $accessOperation);
95+
return NULL;
96+
}
97+
98+
}

tests/src/Kernel/DataProducer/EntityMultipleTest.php

-6
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ public function testResolveEntityLoadMultiple(): void {
7878
'type' => $this->node1->getEntityTypeId(),
7979
'bundles' => [$this->node1->bundle(), $this->node2->bundle()],
8080
'ids' => [$this->node1->id(), $this->node2->id(), $this->node3->id()],
81-
// @todo We need to set these default values here to make the access
82-
// handling work. Ideally that should not be needed.
83-
'access' => TRUE,
84-
'access_operation' => 'view',
8581
]);
8682

8783
$nids = array_values(array_map(function (NodeInterface $item) {
@@ -104,8 +100,6 @@ public function testResolveEntityLoadWithNullId(): void {
104100
$result = $this->executeDataProducer('entity_load_multiple', [
105101
'type' => $this->node1->getEntityTypeId(),
106102
'ids' => [NULL],
107-
'access' => TRUE,
108-
'access_operation' => 'view',
109103
]);
110104

111105
$this->assertSame([], $result);

0 commit comments

Comments
 (0)