From c92785b96084f846f9f635d50a9cdbf4c127c769 Mon Sep 17 00:00:00 2001 From: Daniele Barbaro Date: Tue, 8 Sep 2026 14:31:32 +0200 Subject: [PATCH 1/2] fix(container): use scoped binding for parser KmlParser holds the loaded SimpleXMLElement as instance state, so the singleton binding kept a parsed document alive across Octane requests and across jobs in a long running queue worker. Any consumer resolving the parser without loading first could read another request's document. A scoped binding keeps the instance stable for the whole request or job (so the documented facade chaining still works) while the container flushes it between lifecycles. --- src/KmlParserServiceProvider.php | 8 +++++++- tests/ServiceProviderTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/ServiceProviderTest.php diff --git a/src/KmlParserServiceProvider.php b/src/KmlParserServiceProvider.php index 5a6ecc6..bbf006b 100644 --- a/src/KmlParserServiceProvider.php +++ b/src/KmlParserServiceProvider.php @@ -21,7 +21,13 @@ public function configurePackage(Package $package): void public function packageRegistered(): void { - $this->app->singleton(KmlParser::class, function () { + /* + * The parser keeps the loaded document in memory, so a singleton would + * leak that state across requests under Octane and across jobs in a + * long running queue worker. A scoped binding is resolved once per + * request/job lifecycle and flushed in between. + */ + $this->app->scoped(KmlParser::class, function () { return new KmlParser; }); } diff --git a/tests/ServiceProviderTest.php b/tests/ServiceProviderTest.php new file mode 100644 index 0000000..d358246 --- /dev/null +++ b/tests/ServiceProviderTest.php @@ -0,0 +1,26 @@ +toBe($parser); +}); + +it('resolves a fresh parser once the scope is flushed', function () { + $parser = app(KmlParser::class); + + app()->forgetScopedInstances(); + + expect(app(KmlParser::class))->not->toBe($parser); +}); + +it('does not leak a loaded document across scopes', function () { + app(KmlParser::class)->loadFromFile(__DIR__.'/files/kml-example/base.kml'); + + app()->forgetScopedInstances(); + + app(KmlParser::class)->getPlacemarks(); +})->throws(KmlParserException::class, 'No KML data loaded'); From 973516e8e54f8c098cd729bbfed1a3c7f29485d8 Mon Sep 17 00:00:00 2001 From: danielebarbaro <4376886+danielebarbaro@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:33:26 +0000 Subject: [PATCH 2/2] Fix styling --- src/KmlParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/KmlParser.php b/src/KmlParser.php index c899dd1..e155197 100755 --- a/src/KmlParser.php +++ b/src/KmlParser.php @@ -60,7 +60,7 @@ public function loadFromString(string $content): self $this->xml->registerXPathNamespace('kml', $this->namespace); return $this; - } catch (\Exception $e) { + } catch (Exception $e) { libxml_clear_errors(); throw KmlParserException::failedToParse($e->getMessage()); }