From ada7de1386ef3c2248a3ab171331d31d30cc7609 Mon Sep 17 00:00:00 2001 From: Roman Wolan Date: Fri, 21 Aug 2026 09:40:57 +0200 Subject: [PATCH] fix: prevent GraphQLCollector from accumulating batches across requests GraphQLCollector appends one entry per executed GraphQL operation to $batches, each holding a VarDumper clone of the full response, and nothing cleared it between requests: reset() cleared only $data, and the collector carried no kernel.reset tag, so where it is registered while Symfony's profiler service is not, reset() was never called at all. reset() now clears $batches as well, and profiler.yaml tags the collector kernel.reset so ServicesResetter reaches it directly, the same way Executor and TypeResolver already do (#1203). Unlike the compile-time schema state involved in #1242, $batches is per-request runtime state, so clearing it loses nothing. The base DataCollector serializes only $data into the persisted profile, so the profiler panel is unaffected. --- src/DataCollector/GraphQLCollector.php | 1 + src/Resources/config/profiler.yaml | 1 + tests/DataCollector/GraphQLCollectorTest.php | 34 ++++++++++++++++ .../GraphQLCollectorResetTest.php | 40 +++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 tests/Functional/DataCollector/GraphQLCollectorResetTest.php diff --git a/src/DataCollector/GraphQLCollector.php b/src/DataCollector/GraphQLCollector.php index 577b9b14f..3c635454d 100644 --- a/src/DataCollector/GraphQLCollector.php +++ b/src/DataCollector/GraphQLCollector.php @@ -86,6 +86,7 @@ public function getBatches(): array public function reset(): void { $this->data = []; + $this->batches = []; } /** diff --git a/src/Resources/config/profiler.yaml b/src/Resources/config/profiler.yaml index eccbdb817..8f2eeacdd 100644 --- a/src/Resources/config/profiler.yaml +++ b/src/Resources/config/profiler.yaml @@ -16,3 +16,4 @@ services: template: "@OverblogGraphQL/profiler/panel.html.twig" id: graphql - { name: kernel.event_listener, event: graphql.post_executor, method: onPostExecutor } + - { name: kernel.reset, method: reset } diff --git a/tests/DataCollector/GraphQLCollectorTest.php b/tests/DataCollector/GraphQLCollectorTest.php index 655eae0a8..d81e29b58 100644 --- a/tests/DataCollector/GraphQLCollectorTest.php +++ b/tests/DataCollector/GraphQLCollectorTest.php @@ -66,4 +66,38 @@ public function testCollect(): void ], ]); } + + public function testResetClearsBatches(): void + { + $collector = new GraphQLCollector(); + + $request = new Request(); + + $collector->onPostExecutor(new ExecutorResultEvent( + new ExecutionResult(['res' => 'ok']), + ExecutorArgumentsEvent::create('test_schema', new ExtensibleSchema([]), 'query{ test{field1} }', new ArrayObject()) + )); + + $collector->collect($request, new Response()); + $this->assertCount(1, $collector->getBatches()); + + $collector->reset(); + $collector->collect($request, new Response()); + + $this->assertSame([], $collector->getBatches()); + $this->assertEquals(0, $collector->getCount()); + $this->assertFalse($collector->getError()); + + $collector->onPostExecutor(new ExecutorResultEvent( + new ExecutionResult(['res' => 'ok']), + ExecutorArgumentsEvent::create('test_schema', new ExtensibleSchema([]), 'query{ other{field1, field2} }', new ArrayObject()) + )); + + $collector->collect($request, new Response()); + + $batches = $collector->getBatches(); + $this->assertCount(1, $batches); + $this->assertEquals('query{ other{field1, field2} }', $batches[0]['queryString']); + $this->assertEquals(1, $collector->getCount()); + } } diff --git a/tests/Functional/DataCollector/GraphQLCollectorResetTest.php b/tests/Functional/DataCollector/GraphQLCollectorResetTest.php new file mode 100644 index 000000000..0aaa1f509 --- /dev/null +++ b/tests/Functional/DataCollector/GraphQLCollectorResetTest.php @@ -0,0 +1,40 @@ + 'connection']); + static::sendRequest($client, Introspection::getIntrospectionQuery()); + + $container = static::getContainer(); + // the premise of this test: the collector is registered while Symfony's profiler is not + $this->assertFalse($container->has('profiler')); + /** @var ContainerInterface $testContainer */ + $testContainer = $container->get('test.service_container'); + /** @var GraphQLCollector $collector */ + $collector = $testContainer->get(GraphQLCollector::class); + + $collector->collect(new Request(), new Response()); + $this->assertCount(1, $collector->getBatches()); + + // this is what a long-running runtime does between requests, through Kernel::boot() + /** @var \Symfony\Contracts\Service\ResetInterface $resetter */ + $resetter = $container->get('services_resetter'); + $resetter->reset(); + + $collector->collect(new Request(), new Response()); + $this->assertSame([], $collector->getBatches()); + } +}