Skip to content

Commit cc656ce

Browse files
SkullsneezeMartijn Swinkelsjasonvarga
authored
[5.x] Allow adding of GraphQL mutations (#11908)
Co-authored-by: Martijn Swinkels <martijn.swinkels@wearejust.com> Co-authored-by: Jason Varga <jason@pixelfear.com>
1 parent d4a0853 commit cc656ce

4 files changed

Lines changed: 173 additions & 1 deletion

File tree

config/graphql.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@
4242
//
4343
],
4444

45+
/*
46+
|--------------------------------------------------------------------------
47+
| Mutations
48+
|--------------------------------------------------------------------------
49+
|
50+
| Here you may list mutations to be added to the Statamic schema.
51+
|
52+
| https://statamic.dev/graphql#custom-mutations
53+
*/
54+
55+
'mutations' => [
56+
//
57+
],
58+
4559
/*
4660
|--------------------------------------------------------------------------
4761
| Middleware

src/GraphQL/DefaultSchema.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function getConfig()
4545
{
4646
return [
4747
'query' => $this->getQueries(),
48-
'mutation' => [],
48+
'mutation' => $this->getMutations(),
4949
'middleware' => $this->getMiddleware(),
5050
'method' => ['GET', 'POST'],
5151
];
@@ -82,4 +82,9 @@ private function getMiddleware()
8282
GraphQL::getExtraMiddleware()
8383
);
8484
}
85+
86+
private function getMutations()
87+
{
88+
return config('statamic.graphql.mutations', []);
89+
}
8590
}

src/GraphQL/Middleware/CacheResponse.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ public function handle($request, Closure $next)
1313
return $next($request);
1414
}
1515

16+
if ($this->isMutation($request)) {
17+
return $next($request);
18+
}
19+
1620
$cache = app(ResponseCache::class);
1721

1822
if ($response = $cache->get($request)) {
@@ -25,4 +29,11 @@ public function handle($request, Closure $next)
2529

2630
return $response;
2731
}
32+
33+
protected function isMutation($request): bool
34+
{
35+
$query = ltrim(strtolower($request->get('query', '')));
36+
37+
return str_starts_with($query, 'mutation');
38+
}
2839
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
namespace Tests\Feature\GraphQL;
4+
5+
use GraphQL\Type\Definition\Type;
6+
use Orchestra\Testbench\Attributes\DefineEnvironment;
7+
use PHPUnit\Framework\Attributes\Group;
8+
use PHPUnit\Framework\Attributes\Test;
9+
use Rebing\GraphQL\Support\Mutation;
10+
use Statamic\Facades\GraphQL;
11+
use Tests\TestCase;
12+
13+
#[Group('graphql')]
14+
class CustomMutationTest extends TestCase
15+
{
16+
public function setUp(): void
17+
{
18+
parent::setUp();
19+
20+
app()->instance('mutation-count', 0);
21+
}
22+
23+
#[Test]
24+
public function custom_mutation_does_not_yet_exist()
25+
{
26+
$this
27+
->post('/graphql', ['query' => 'mutation { createItem(name: "test") }'])
28+
->assertJson(['errors' => [[
29+
'message' => 'Schema is not configured for mutations.',
30+
]]]);
31+
}
32+
33+
#[Test]
34+
#[DefineEnvironment('addCustomMutationsThroughConfig')]
35+
public function a_custom_mutation_can_be_added_to_the_default_schema_through_config()
36+
{
37+
$this
38+
->post('/graphql', ['query' => 'mutation { createItem(name: "test") }'])
39+
->assertGqlOk()
40+
->assertExactJson(['data' => ['createItem' => 'Item created: test']]);
41+
}
42+
43+
#[Test]
44+
#[DefineEnvironment('addCustomMutationsThroughConfig')]
45+
public function multiple_custom_mutations_can_be_added()
46+
{
47+
$this
48+
->post('/graphql', ['query' => 'mutation { createItem(name: "first") }'])
49+
->assertGqlOk()
50+
->assertExactJson(['data' => ['createItem' => 'Item created: first']]);
51+
52+
$this
53+
->post('/graphql', ['query' => 'mutation { updateItem(id: 1, name: "updated") }'])
54+
->assertGqlOk()
55+
->assertExactJson(['data' => ['updateItem' => 'Item 1 updated: updated']]);
56+
}
57+
58+
#[Test]
59+
#[DefineEnvironment('addCustomMutationsThroughConfig')]
60+
public function mutations_are_not_cached()
61+
{
62+
$this
63+
->post('/graphql', ['query' => 'mutation { createItem(name: "test") }'])
64+
->assertGqlOk()
65+
->assertExactJson(['data' => ['createItem' => 'Item created: test']]);
66+
67+
$this
68+
->post('/graphql', ['query' => 'mutation { createItem(name: "test") }'])
69+
->assertGqlOk()
70+
->assertExactJson(['data' => ['createItem' => 'Item created: test']]);
71+
72+
$this->assertEquals(2, app('mutation-count'));
73+
}
74+
75+
protected function addCustomMutationsThroughConfig($app)
76+
{
77+
$app['config']->set('statamic.graphql.mutations', [
78+
CreateItemMutation::class,
79+
UpdateItemMutation::class,
80+
]);
81+
}
82+
}
83+
84+
class CreateItemMutation extends Mutation
85+
{
86+
protected $attributes = [
87+
'name' => 'createItem',
88+
];
89+
90+
public function type(): Type
91+
{
92+
return GraphQL::string();
93+
}
94+
95+
public function args(): array
96+
{
97+
return [
98+
'name' => [
99+
'type' => Type::nonNull(Type::string()),
100+
'description' => 'The name of the item to create',
101+
],
102+
];
103+
}
104+
105+
public function resolve($root, $args)
106+
{
107+
app()->instance('mutation-count', app('mutation-count') + 1);
108+
109+
return 'Item created: '.$args['name'];
110+
}
111+
}
112+
113+
class UpdateItemMutation extends Mutation
114+
{
115+
protected $attributes = [
116+
'name' => 'updateItem',
117+
];
118+
119+
public function type(): Type
120+
{
121+
return GraphQL::string();
122+
}
123+
124+
public function args(): array
125+
{
126+
return [
127+
'id' => [
128+
'type' => Type::nonNull(Type::int()),
129+
'description' => 'The ID of the item to update',
130+
],
131+
'name' => [
132+
'type' => Type::nonNull(Type::string()),
133+
'description' => 'The new name of the item',
134+
],
135+
];
136+
}
137+
138+
public function resolve($root, $args)
139+
{
140+
return "Item {$args['id']} updated: {$args['name']}";
141+
}
142+
}

0 commit comments

Comments
 (0)