From 8de2e3c847862852375f7a2930f48d42b009fcbd Mon Sep 17 00:00:00 2001 From: "Alex M." Date: Tue, 11 Aug 2026 18:17:47 +0200 Subject: [PATCH 1/6] perf(http): lazy load DomCrawler instance on demand in Response --- src/Http/Response.php | 12 ++++++++---- tests/Http/ResponseTest.php | 9 +++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index ab4012a..f005f1d 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -28,18 +28,22 @@ final class Response implements DroppableInterface use HasMetaData; use Droppable; - private Crawler $crawler; + private ?Crawler $crawler = null; public function __construct( private ResponseInterface $response, private Request $request, ) { - $this->crawler = new Crawler((string) $response->getBody(), $request->getUri()); + } + + public function getCrawler(): Crawler + { + return $this->crawler ??= new Crawler((string) $this->response->getBody(), $this->request->getUri()); } public function __call(string $method, array $args): mixed { - return $this->crawler->{$method}(...$args); + return $this->getCrawler()->{$method}(...$args); } public function getRequest(): Request @@ -60,7 +64,7 @@ public function getBody(): string public function withBody(string $body): self { $this->response = $this->response->withBody(Utils::streamFor($body)); - $this->crawler = new Crawler($body, $this->request->getUri()); + $this->crawler = null; return $this; } diff --git a/tests/Http/ResponseTest.php b/tests/Http/ResponseTest.php index 14e3503..714813f 100644 --- a/tests/Http/ResponseTest.php +++ b/tests/Http/ResponseTest.php @@ -125,6 +125,15 @@ public function testUpdatingResponseBodyUpdatesCrawler(): void self::assertSame('New', $response->filter('p')->text('')); } + public function testDomCrawlerIsLazyLoadedOnDemand(): void + { + $response = $this->makeResponse(body: '{"status":"ok"}'); + + self::assertSame('{"status":"ok"}', $response->getBody()); + self::assertSame(200, $response->getStatus()); + self::assertSame(0, $response->filter('p')->count()); + } + protected function createDroppable(): DroppableInterface { return $this->makeResponse( From 711335dd20580dc687c34aa871aaeb8ed51b65f5 Mon Sep 17 00:00:00 2001 From: "Alex M." Date: Tue, 11 Aug 2026 22:03:59 +0200 Subject: [PATCH 2/6] fix(http): resolve PHPUnit 10 DataProvider compatibility, cs-fix style, and lazy loading assertion --- src/Http/Response.php | 2 +- tests/Http/ResponseTest.php | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index f005f1d..f2da5e9 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -25,8 +25,8 @@ */ final class Response implements DroppableInterface { - use HasMetaData; use Droppable; + use HasMetaData; private ?Crawler $crawler = null; diff --git a/tests/Http/ResponseTest.php b/tests/Http/ResponseTest.php index 714813f..3c74262 100644 --- a/tests/Http/ResponseTest.php +++ b/tests/Http/ResponseTest.php @@ -14,11 +14,14 @@ namespace RoachPHP\Tests\Http; use GuzzleHttp\Psr7\Stream; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use ReflectionProperty; use RoachPHP\Http\Response; use RoachPHP\Support\DroppableInterface; use RoachPHP\Testing\Concerns\InteractsWithRequestsAndResponses; use RoachPHP\Tests\Support\DroppableTestCase; +use Symfony\Component\DomCrawler\Crawler; /** * @internal @@ -37,6 +40,7 @@ public function testCanAccessDomCrawlerDirectlyFromResponse(): void self::assertCount(1, $links); } + #[DataProvider('responseCodeProvider')] /** * @dataProvider responseCodeProvider */ @@ -60,6 +64,7 @@ public static function responseCodeProvider(): iterable ]; } + #[DataProvider('responseBodyProvider')] /** * @dataProvider responseBodyProvider */ @@ -129,9 +134,12 @@ public function testDomCrawlerIsLazyLoadedOnDemand(): void { $response = $this->makeResponse(body: '{"status":"ok"}'); - self::assertSame('{"status":"ok"}', $response->getBody()); - self::assertSame(200, $response->getStatus()); + $crawlerProperty = new ReflectionProperty(Response::class, 'crawler'); + + self::assertNull($crawlerProperty->getValue($response)); + self::assertSame(0, $response->filter('p')->count()); + self::assertInstanceOf(Crawler::class, $crawlerProperty->getValue($response)); } protected function createDroppable(): DroppableInterface From 9c70c4145e8af330f2be4e6062faef5b28a5fc54 Mon Sep 17 00:00:00 2001 From: "Alex M." Date: Tue, 11 Aug 2026 22:16:41 +0200 Subject: [PATCH 3/6] fix(http): resolve CS-Fixer method order and HTML body in ResponseTest --- src/Http/Response.php | 8 ++++---- tests/Http/ResponseTest.php | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index f2da5e9..28e827f 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -36,14 +36,14 @@ public function __construct( ) { } - public function getCrawler(): Crawler + public function __call(string $method, array $args): mixed { - return $this->crawler ??= new Crawler((string) $this->response->getBody(), $this->request->getUri()); + return $this->getCrawler()->{$method}(...$args); } - public function __call(string $method, array $args): mixed + public function getCrawler(): Crawler { - return $this->getCrawler()->{$method}(...$args); + return $this->crawler ??= new Crawler((string) $this->response->getBody(), $this->request->getUri()); } public function getRequest(): Request diff --git a/tests/Http/ResponseTest.php b/tests/Http/ResponseTest.php index 3c74262..8cd0206 100644 --- a/tests/Http/ResponseTest.php +++ b/tests/Http/ResponseTest.php @@ -16,7 +16,6 @@ use GuzzleHttp\Psr7\Stream; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -use ReflectionProperty; use RoachPHP\Http\Response; use RoachPHP\Support\DroppableInterface; use RoachPHP\Testing\Concerns\InteractsWithRequestsAndResponses; @@ -132,9 +131,9 @@ public function testUpdatingResponseBodyUpdatesCrawler(): void public function testDomCrawlerIsLazyLoadedOnDemand(): void { - $response = $this->makeResponse(body: '{"status":"ok"}'); + $response = $this->makeResponse(body: ''); - $crawlerProperty = new ReflectionProperty(Response::class, 'crawler'); + $crawlerProperty = new \ReflectionProperty(Response::class, 'crawler'); self::assertNull($crawlerProperty->getValue($response)); From c4320f8d01d1636fc51933a4592d0eaef398681c Mon Sep 17 00:00:00 2001 From: "Alex M." Date: Tue, 11 Aug 2026 22:19:06 +0200 Subject: [PATCH 4/6] ci: fix checkout ref in fix-style workflow --- .github/workflows/fix-style.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/fix-style.yml b/.github/workflows/fix-style.yml index 7907c3e..c72e0f7 100644 --- a/.github/workflows/fix-style.yml +++ b/.github/workflows/fix-style.yml @@ -13,8 +13,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - name: Setup PHP uses: shivammathur/setup-php@v2 From 43e7ada929a96b07742a9055e951ad67faca344a Mon Sep 17 00:00:00 2001 From: "Alex M." Date: Tue, 11 Aug 2026 22:24:39 +0200 Subject: [PATCH 5/6] ci: add assertIsResource for PHPStan strict resource checks --- tests/Http/ResponseTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Http/ResponseTest.php b/tests/Http/ResponseTest.php index 8cd0206..06946dc 100644 --- a/tests/Http/ResponseTest.php +++ b/tests/Http/ResponseTest.php @@ -85,6 +85,7 @@ public static function responseBodyProvider(): iterable 'stream' => [static function (string $body) { $stream = \fopen('php://memory', 'r+b'); + self::assertIsResource($stream); \fwrite($stream, $body); \rewind($stream); @@ -93,6 +94,7 @@ public static function responseBodyProvider(): iterable 'StreamInterface' => [static function (string $body) { $stream = \fopen('php://memory', 'r+b'); + self::assertIsResource($stream); \fwrite($stream, $body); \rewind($stream); From 3a461d8731ced51f0001f42e6ba00cca3d6ff04b Mon Sep 17 00:00:00 2001 From: "Alex M." Date: Tue, 11 Aug 2026 22:27:30 +0200 Subject: [PATCH 6/6] fix(http): add explicit string cast to getUri and param docblock for PHPStan level 9 --- src/Http/Response.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index 28e827f..27f25f6 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -36,6 +36,9 @@ public function __construct( ) { } + /** + * @param array $args + */ public function __call(string $method, array $args): mixed { return $this->getCrawler()->{$method}(...$args); @@ -43,7 +46,7 @@ public function __call(string $method, array $args): mixed public function getCrawler(): Crawler { - return $this->crawler ??= new Crawler((string) $this->response->getBody(), $this->request->getUri()); + return $this->crawler ??= new Crawler((string) $this->response->getBody(), (string) $this->request->getUri()); } public function getRequest(): Request