diff --git a/composer.json b/composer.json index b4b7486..5d0ad01 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "ergebnis/composer-normalize": "^2.45", "ergebnis/php-cs-fixer-config": "^6.45.0", "http-interop/http-factory-guzzle": "^1.2", - "phpstan/phpstan": "^2.1", + "phpstan/phpstan": "^2.1.29", "phpunit/phpunit": "^10.4.2", "psr/http-message": "^1.1.0", "roave/security-advisories": "dev-latest", diff --git a/composer.lock b/composer.lock index 7a39670..8e9d103 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "565d61d07bd97d87e3532b1ed4068a9b", + "content-hash": "aee87adf84ba5e52da0a3528ddd23377", "packages": [ { "name": "guzzlehttp/guzzle", @@ -4030,16 +4030,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.11", + "version": "2.1.29", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30" + "url": "https://github.com/phpstan/phpstan-phar-composer-source.git", + "reference": "git" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d618573eed4a1b6b75e37b2e0b65ac65c885d88e", + "reference": "d618573eed4a1b6b75e37b2e0b65ac65c885d88e", "shasum": "" }, "require": { @@ -4084,7 +4084,7 @@ "type": "github" } ], - "time": "2025-03-24T13:45:00+00:00" + "time": "2025-09-25T06:58:18+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/Core/Engine.php b/src/Core/Engine.php index 5650ba0..ec884ad 100644 --- a/src/Core/Engine.php +++ b/src/Core/Engine.php @@ -49,6 +49,8 @@ public function collect(Run $run): array $this->start($run); + $this->eventDispatcher->removeSubscriber($extension); + return $extension->getScrapedItems(); } @@ -68,6 +70,8 @@ public function start(Run $run): void } $this->work($run); + + $this->cleanup($run); } private function work(Run $run): void @@ -156,4 +160,11 @@ private function configure(Run $run): void $this->eventDispatcher->addSubscriber($extension); } } + + private function cleanup(Run $run): void + { + foreach ($run->extensions as $extension) { + $this->eventDispatcher->removeSubscriber($extension); + } + } } diff --git a/src/Downloader/Middleware/RequestDeduplicationMiddleware.php b/src/Downloader/Middleware/RequestDeduplicationMiddleware.php index 2d0dbaf..ddcab5f 100644 --- a/src/Downloader/Middleware/RequestDeduplicationMiddleware.php +++ b/src/Downloader/Middleware/RequestDeduplicationMiddleware.php @@ -48,7 +48,6 @@ public function handleRequest(Request $request): Request $replaceFlags |= \HTTP_URL_STRIP_QUERY; } - /** @phpstan-ignore argument.type */ $uri = http_build_url($uri, $parts, $replaceFlags); if (\in_array($uri, $this->seenUris, true)) { diff --git a/src/Testing/FakeLogger.php b/src/Testing/FakeLogger.php index b7ab67f..88dd476 100644 --- a/src/Testing/FakeLogger.php +++ b/src/Testing/FakeLogger.php @@ -132,4 +132,19 @@ public function messageWasLogged(string $level, string $message, ?array $context && (null === $context || $log['context'] === $context); })); } + + /** + * @param array $context + */ + public function countMessages(string $level, string $message, ?array $context = null): int + { + if (empty($this->logs[$level])) { + return 0; + } + + return \count(\array_filter($this->logs[$level], static function (array $log) use ($message, $context) { + return $log['message'] === $message + && (null === $context || $log['context'] === $context); + })); + } } diff --git a/tests/Core/EngineTest.php b/tests/Core/EngineTest.php index a34bb3d..d87cbb6 100644 --- a/tests/Core/EngineTest.php +++ b/tests/Core/EngineTest.php @@ -214,6 +214,40 @@ public function testRegisterExtensions(): void ])); } + public function testOnlyOneMessageLoggedPerRequestWhenSpiderDispatchedBackToBack(): void + { + $url = 'http://localhost:8000/test1'; + + $logger = new FakeLogger(); + $run = new Run( + [ + $this->makeRequest($url), + ], + '::namespace::', + extensions: [new LoggerExtension($logger)], + ); + + $logger2 = new FakeLogger(); + $run2 = new Run( + [ + $this->makeRequest($url), + ], + '::namespace::', + extensions: [new LoggerExtension($logger2)], + ); + + $this->engine->start($run); + + self::assertSame(1, $logger->countMessages('info', 'Dispatching request', ['uri' => $url])); + + $this->engine->start($run2); + + self::assertSame(1, $logger2->countMessages('info', 'Dispatching request', ['uri' => $url])); + + // Verify no duplicate messages in the first logger (should still be 1) + self::assertSame(1, $logger->countMessages('info', 'Dispatching request', ['uri' => $url])); + } + public function testCollectAndReturnScrapedItems(): void { $parseCallback = static function () {