diff --git a/phpstan.neon b/phpstan.neon index a8cdb4b..b505c91 100755 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,10 @@ parameters: - level: 5 + level: 8 checkMissingCallableSignature: true paths: - src + ignoreErrors: + - + identifier: missingType.generics + - + identifier: missingType.iterableValue diff --git a/src/Container.php b/src/Container.php index 4a3b278..dcb077f 100644 --- a/src/Container.php +++ b/src/Container.php @@ -107,14 +107,14 @@ public function start(): void public function getHost(): string { - $this->ensureRunning(); - return $this->container->getHost(); + $startedGenericContainer = $this->runningContainer(); + return $startedGenericContainer->getHost(); } public function getMappedPort(): int { - $this->ensureRunning(); - return $this->container->getMappedPort($this->internalPort); + $startedGenericContainer = $this->runningContainer(); + return $startedGenericContainer->getMappedPort($this->internalPort); } public function getBaseUrl(): string @@ -148,10 +148,12 @@ public function getClient(): Client return new Client($baseUrl, $this->apiToken); } - private function ensureRunning(): void + private function runningContainer(): StartedGenericContainer { if (!$this->container instanceof StartedGenericContainer) { throw new RuntimeException('Container must be running'); } + + return $this->container; } } diff --git a/src/Stream/CurlMultiHandler.php b/src/Stream/CurlMultiHandler.php index 7e7389f..49c691a 100644 --- a/src/Stream/CurlMultiHandler.php +++ b/src/Stream/CurlMultiHandler.php @@ -10,8 +10,8 @@ class CurlMultiHandler { - private ?CurlHandle $handle = null; - private ?CurlMultiHandle $multiHandle = null; + private ?CurlHandle $curlHandle = null; + private ?CurlMultiHandle $curlMultiHandle = null; private float $abortIn = 0.0; private float $iteratorTime; private ?Queue $header = null; @@ -43,7 +43,7 @@ public function getWriteQueue(): Queue public function addHandle(Request $request): void { - $handle = curl_init(); + $curlHandle = curl_init(); $this->header = new Queue(maxSize: 100); $this->write = new Queue(); @@ -54,50 +54,41 @@ public function addHandle(Request $request): void $this->write, ); - if (!curl_setopt_array($handle, $options)) { - throw new RuntimeException('Internal HttpClient: Failed to set cURL options: ' . curl_error($handle)); + if (!curl_setopt_array($curlHandle, $options)) { + throw new RuntimeException('Internal HttpClient: Failed to set cURL options: ' . curl_error($curlHandle)); } - $this->handle = $handle; + $this->curlHandle = $curlHandle; } public function execute(): void { - if (!$this->handle instanceof CurlHandle) { - throw new RuntimeException('Internal HttpClient: No handle to execute.'); - } - - if (!$this->header instanceof Queue) { - throw new RuntimeException('Internal HttpClient: No header queue available.'); - } + $curlHandle = $this->curlHandle(); + $queue = $this->getHeaderQueue(); - $multiHandle = curl_multi_init(); - if (curl_multi_add_handle($multiHandle, $this->handle) !== CURLM_OK) { - throw new RuntimeException('Internal HttpClient: Failed to add cURL handle to multi handle: ' . curl_multi_strerror(curl_multi_errno($multiHandle))); + $curlMultiHandle = curl_multi_init(); + if (curl_multi_add_handle($curlMultiHandle, $curlHandle) !== CURLM_OK) { + throw new RuntimeException('Internal HttpClient: Failed to add cURL handle to multi handle: ' . curl_multi_strerror(curl_multi_errno($curlMultiHandle))); } do { - $status = curl_multi_exec($multiHandle, $isRunning); + $status = curl_multi_exec($curlMultiHandle, $isRunning); if ($isRunning) { - curl_multi_select($multiHandle); + curl_multi_select($curlMultiHandle); } - $this->verifyCurlHandle($multiHandle); + $this->verifyCurlHandle($curlMultiHandle); - } while ($this->header->isEmpty() && $isRunning && $status === CURLM_OK); + } while ($queue->isEmpty() && $isRunning && $status === CURLM_OK); - $this->multiHandle = $multiHandle; + $this->curlMultiHandle = $curlMultiHandle; } public function contentIterator(): iterable { - if (!$this->multiHandle instanceof CurlMultiHandle) { - throw new RuntimeException('Internal HttpClient: No multi handle to execute.'); - } - - if (!$this->write instanceof Queue) { - throw new RuntimeException('Internal HttpClient: No write queue available.'); - } + $curlHandle = $this->curlHandle(); + $curlMultiHandle = $this->curlMultiHandle(); + $queue = $this->getWriteQueue(); $this->iteratorTime = microtime(true); @@ -109,31 +100,31 @@ public function contentIterator(): iterable break; } - $status = curl_multi_exec($this->multiHandle, $isRunning); + $status = curl_multi_exec($curlMultiHandle, $isRunning); if ($isRunning) { - curl_multi_select($this->multiHandle); + curl_multi_select($curlMultiHandle); } - $this->verifyCurlHandle($this->multiHandle); + $this->verifyCurlHandle($curlMultiHandle); - while (!$this->write->isEmpty()) { - yield $this->write->read(); + while (!$queue->isEmpty()) { + yield $queue->read(); } } while ($isRunning && $status === CURLM_OK); - curl_multi_remove_handle($this->multiHandle, $this->handle); - curl_multi_close($this->multiHandle); - curl_close($this->handle); + curl_multi_remove_handle($curlMultiHandle, $curlHandle); + curl_multi_close($curlMultiHandle); + curl_close($curlHandle); unset( - $this->handle, - $this->multiHandle, + $this->curlHandle, + $this->curlMultiHandle, $this->header, $this->write, ); - $this->handle = null; - $this->multiHandle = null; + $this->curlHandle = null; + $this->curlMultiHandle = null; $this->header = null; $this->write = null; } @@ -145,13 +136,31 @@ private function verifyCurlHandle(CurlMultiHandle $curlMultiHandle): void return; } - $handle = $info['handle'] ?? null; - if (!$handle instanceof CurlHandle) { + $curlHandle = $info['handle'] ?? null; + if (!$curlHandle instanceof CurlHandle) { throw new RuntimeException('Internal HttpClient: cURL handle info read returned an invalid handle.'); } - if (curl_errno($handle) !== 0) { - throw new RuntimeException('Internal HttpClient: cURL handle execution failed with error: ' . curl_error($handle)); + if (curl_errno($curlHandle) !== 0) { + throw new RuntimeException('Internal HttpClient: cURL handle execution failed with error: ' . curl_error($curlHandle)); } } + + private function curlHandle(): CurlHandle + { + if (!$this->curlHandle instanceof CurlHandle) { + throw new RuntimeException('Internal HttpClient: No handle available.'); + } + + return $this->curlHandle; + } + + private function curlMultiHandle(): CurlMultiHandle + { + if (!$this->curlMultiHandle instanceof CurlMultiHandle) { + throw new RuntimeException('Internal HttpClient: No multi handle available.'); + } + + return $this->curlMultiHandle; + } } diff --git a/src/Stream/HttpClient.php b/src/Stream/HttpClient.php index 9488045..cade6ee 100644 --- a/src/Stream/HttpClient.php +++ b/src/Stream/HttpClient.php @@ -50,7 +50,12 @@ public function buildBody(null|array|object $body): string return ''; } - return json_encode($body); + $json = json_encode($body); + if ($json === false) { + throw new InvalidArgumentException('Internal HttpClient: Failed to encode body to JSON: ' . json_last_error_msg()); + } + + return $json; } public function get(string $uri, ?string $apiToken = null): Response diff --git a/src/Stream/Response.php b/src/Stream/Response.php index 90c25c6..9f9526b 100755 --- a/src/Stream/Response.php +++ b/src/Stream/Response.php @@ -88,6 +88,10 @@ public function __construct( public function getStream(): Stream { + if (!$this->stream instanceof Stream) { + throw new InvalidArgumentException('Internal HttpClient: No stream available in response.'); + } + return $this->stream; } diff --git a/tests/Stream/CurlMultiHandlerTest.php b/tests/Stream/CurlMultiHandlerTest.php index 57c4b4d..630141c 100644 --- a/tests/Stream/CurlMultiHandlerTest.php +++ b/tests/Stream/CurlMultiHandlerTest.php @@ -24,6 +24,14 @@ public function getPropertyValue(object $object, string $propertyName): mixed return $reflectionProperty->getValue($object); } + public function setPropertyValue(object $object, string $propertyName, mixed $propertyValue): void + { + $reflectionClass = new ReflectionClass($object); + $reflectionProperty = $reflectionClass->getProperty($propertyName); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($object, $propertyValue); + } + public function removeLineBrakes(string $line): string { return preg_replace('/\r\n|\r|\n/', '', $line); @@ -74,13 +82,13 @@ public function testAddHandleSetsQueuesAndHandle(): void $this->assertInstanceOf(Queue::class, $this->getPropertyValue($curlMultiHandler, 'header')); $this->assertInstanceOf(Queue::class, $this->getPropertyValue($curlMultiHandler, 'write')); - $this->assertNotNull($this->getPropertyValue($curlMultiHandler, 'handle')); + $this->assertNotNull($this->getPropertyValue($curlMultiHandler, 'curlHandle')); } public function testExecuteThrowsIfHandleMissing(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Internal HttpClient: No handle to execute.'); + $this->expectExceptionMessage('Internal HttpClient: No handle available.'); $curlMultiHandler = new CurlMultiHandler(); $curlMultiHandler->execute(); @@ -121,12 +129,24 @@ public function testExecuteSendsRequestAndParsesHttpHeadersCorrectly(): void $this->assertSame('Content-Type: application/json', $this->removeLineBrakes($headerQueue->read())); } + public function testContentIteratorThrowsIfHandleMissing(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Internal HttpClient: No handle available.'); + + $curlMultiHandler = new CurlMultiHandler(); + iterator_count($curlMultiHandler->contentIterator()); + } + public function testContentIteratorThrowsIfMultiHandleMissing(): void { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Internal HttpClient: No multi handle to execute.'); + $this->expectExceptionMessage('Internal HttpClient: No multi handle available.'); $curlMultiHandler = new CurlMultiHandler(); + + $this->setPropertyValue($curlMultiHandler, 'curlHandle', curl_init()); + iterator_count($curlMultiHandler->contentIterator()); } @@ -137,10 +157,8 @@ public function testContentIteratorThrowsIfWriteQueueMissing(): void $curlMultiHandler = new CurlMultiHandler(); - $reflectionClass = new ReflectionClass($curlMultiHandler); - $reflectionProperty = $reflectionClass->getProperty('multiHandle'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($curlMultiHandler, curl_multi_init()); + $this->setPropertyValue($curlMultiHandler, 'curlHandle', curl_init()); + $this->setPropertyValue($curlMultiHandler, 'curlMultiHandle', curl_multi_init()); iterator_count($curlMultiHandler->contentIterator()); }