From ed5b91b6764024cf75e1053d678eefb121a4361d Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Mon, 14 Sep 2026 14:39:48 +0200 Subject: [PATCH 1/2] chore: upgrade PHPStan to 2.x and fix reported errors Upgrade phpstan/phpstan to ^2.2 and phpstan/phpstan-phpunit to ^2.0. Fix errors reported against the updated docker-php-api types and the stricter list<> inference in PHPStan 2: - Build EndpointSettings via setters (no array constructor anymore) - Drop dead nullsafe calls on execStart/containerLogs responses - Type aliases, mounts and port bindings as lists - Guard array_key_first() returning null in getFirstMappedPort() - Pass a correct depth to json_decode() in OpenSearchContainerTest - Remove redundant type checks and assertions in tests - Remove ReflectionProperty::setAccessible() calls (no-op since 8.1, deprecated in PHP 8.5) Co-Authored-By: Claude Fable 5.1 --- composer.json | 6 +++--- src/Container/GenericContainer.php | 18 +++++++++--------- src/Container/StartedGenericContainer.php | 11 +++++++---- src/Utils/PortNormalizer.php | 2 +- src/Wait/WaitForExec.php | 2 +- src/Wait/WaitForHttp.php | 5 ++++- tests/Integration/OpenSearchContainerTest.php | 4 ++-- .../StartedGenericContainerTest.php | 1 - .../DockerContainerClientTest.php | 5 ----- tests/Unit/Utils/HostResolverTest.php | 4 ++-- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index 19ed465..cfc6255 100644 --- a/composer.json +++ b/composer.json @@ -26,9 +26,9 @@ "phpunit/phpunit": "^9.5", "brianium/paratest": "^6.11", "friendsofphp/php-cs-fixer": "^3.92", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^2.2", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/extension-installer": "^1.4", "predis/predis": "^3.0 || ^2.0" }, "suggest": { diff --git a/src/Container/GenericContainer.php b/src/Container/GenericContainer.php index d9cf72e..34c6408 100644 --- a/src/Container/GenericContainer.php +++ b/src/Container/GenericContainer.php @@ -66,7 +66,7 @@ class GenericContainer implements TestContainer protected ?string $networkName = null; - /** @var array */ + /** @var list */ protected array $aliases = []; protected ?string $user = null; @@ -92,7 +92,7 @@ class GenericContainer implements TestContainer protected const MAX_START_ATTEMPTS = 2; /** - * @var array + * @var list */ protected array $mounts = []; @@ -290,7 +290,7 @@ public function withNetwork(string $networkName): static */ public function withAliases(array $aliases): static { - $this->aliases = $aliases; + $this->aliases = array_values($aliases); return $this; } @@ -444,15 +444,15 @@ protected function createContainerConfig(): ContainersCreatePostBody if ($this->networkName !== null) { $networkingConfig = new NetworkingConfig(); - $aliases = $this->aliases ?? []; + $aliases = $this->aliases; if ($this->name) { $aliases[] = $this->name; } + $endpointSettings = new EndpointSettings(); + $endpointSettings->setNetworkID($this->networkName); + $endpointSettings->setAliases($aliases); $endpointsConfig = [ - $this->networkName => new EndpointSettings([ - 'networkID' => $this->networkName, - 'aliases' => $aliases, - ]), + $this->networkName => $endpointSettings, ]; $networkingConfig->setEndpointsConfig($endpointsConfig); $containerCreatePostBody->setNetworkingConfig($networkingConfig); @@ -497,7 +497,7 @@ protected function createHostConfig(): ?HostConfig } /** - * @return array> + * @return array> */ protected function createPortBindings(): array { diff --git a/src/Container/StartedGenericContainer.php b/src/Container/StartedGenericContainer.php index 39f4c06..d79eed0 100644 --- a/src/Container/StartedGenericContainer.php +++ b/src/Container/StartedGenericContainer.php @@ -70,8 +70,8 @@ public function exec(array $command): string $contents = $this->dockerClient ->execStart($this->lastExecId, $startConfig, Client::FETCH_RESPONSE) - ?->getBody() - ->getContents() ?? ''; + ->getBody() + ->getContents(); return $this->sanitizeOutput($contents); } @@ -99,8 +99,8 @@ public function logs(): string ['stdout' => true, 'stderr' => true], DockerRuntimeClient::FETCH_RESPONSE ) - ?->getBody() - ->getContents() ?? ''; + ->getBody() + ->getContents(); /** * @var string|false $converted @@ -131,6 +131,9 @@ public function getFirstMappedPort(): int { $ports = (array) $this->getBoundPorts(); $port = array_key_first($ports); + if ($port === null) { + throw new RuntimeException('Failed to get first mapped port for container'); + } /** @var PortBinding | null $firstPortBinding */ $firstPortBinding = $ports[$port][0] ?? null; $firstMappedPort = $firstPortBinding?->getHostPort(); diff --git a/src/Utils/PortNormalizer.php b/src/Utils/PortNormalizer.php index d9cded5..e95c904 100644 --- a/src/Utils/PortNormalizer.php +++ b/src/Utils/PortNormalizer.php @@ -23,7 +23,7 @@ public static function normalizePort(string|int $port, InternetProtocol $interne } // Check if the port specification already includes a protocol - if (is_string($port) && !str_contains($port, '/')) { + if (!str_contains($port, '/')) { return "{$port}/{$internetProtocol->toDockerNotation()}"; } diff --git a/src/Wait/WaitForExec.php b/src/Wait/WaitForExec.php index ce7af35..99a233b 100644 --- a/src/Wait/WaitForExec.php +++ b/src/Wait/WaitForExec.php @@ -15,7 +15,7 @@ class WaitForExec extends BaseWaitStrategy { /** - * @param array $command + * @param list $command */ public function __construct( protected array $command, diff --git a/src/Wait/WaitForHttp.php b/src/Wait/WaitForHttp.php index 604723e..8fe34d5 100644 --- a/src/Wait/WaitForHttp.php +++ b/src/Wait/WaitForHttp.php @@ -106,7 +106,7 @@ public function wait(StartedTestContainer $container): void $this->resolveHostPort($container); $containerAddress = $container->getHost(); - $url = sprintf('%s://%s:%d%s', $this->protocol, $containerAddress, $this->hostPort, $this->path); + $url = "{$this->protocol}://{$containerAddress}:{$this->hostPort}{$this->path}"; $responseCode = $this->makeHttpRequest($url); if ($responseCode === $this->expectedStatusCode) { @@ -120,6 +120,9 @@ public function wait(StartedTestContainer $container): void } } + /** + * @param non-empty-string $url + */ private function makeHttpRequest(string $url): int { $ch = curl_init(); diff --git a/tests/Integration/OpenSearchContainerTest.php b/tests/Integration/OpenSearchContainerTest.php index b1134a5..38e2ef1 100644 --- a/tests/Integration/OpenSearchContainerTest.php +++ b/tests/Integration/OpenSearchContainerTest.php @@ -32,9 +32,9 @@ public function testOpenSearch(): void $this->assertNotEmpty($response); - /** @var array{cluster_name: string} $data */ - $data = json_decode($response, true, JSON_THROW_ON_ERROR, JSON_THROW_ON_ERROR); + $data = json_decode($response, true, 512, JSON_THROW_ON_ERROR); + $this->assertIsArray($data); $this->assertArrayHasKey('cluster_name', $data); $this->assertEquals('docker-cluster', $data['cluster_name']); diff --git a/tests/Integration/StartedGenericContainerTest.php b/tests/Integration/StartedGenericContainerTest.php index a59976c..d3af277 100644 --- a/tests/Integration/StartedGenericContainerTest.php +++ b/tests/Integration/StartedGenericContainerTest.php @@ -58,7 +58,6 @@ public function testShouldStopContainer(): void $stoppedContainer = $container->stop(); - self::assertNotNull($stoppedContainer, 'Stopped container should not be null'); self::assertSame( $container->getId(), $stoppedContainer->getId(), diff --git a/tests/Unit/ContainerClient/DockerContainerClientTest.php b/tests/Unit/ContainerClient/DockerContainerClientTest.php index 9ba3104..be9a3c5 100644 --- a/tests/Unit/ContainerClient/DockerContainerClientTest.php +++ b/tests/Unit/ContainerClient/DockerContainerClientTest.php @@ -52,7 +52,6 @@ private function resetState(): void { $reflection = new \ReflectionClass(DockerContainerClient::class); $property = $reflection->getProperty('dockerClient'); - $property->setAccessible(true); $property->setValue(null, null); DockerContainerClient::resetFactories(); @@ -101,7 +100,6 @@ static function (ClientInterface $httpClient) use (&$capturedHttpClient): Docker private function getPlugins(PluginClient $client): array { $prop = (new \ReflectionClass(PluginClient::class))->getProperty('plugins'); - $prop->setAccessible(true); /** @var \Http\Client\Common\Plugin[] $plugins */ $plugins = $prop->getValue($client); @@ -131,7 +129,6 @@ private function findHeaderDefaultsPlugin(PluginClient $client): ?HeaderDefaults private function getHeadersFromPlugin(HeaderDefaultsPlugin $plugin): array { $prop = (new \ReflectionClass(HeaderDefaultsPlugin::class))->getProperty('headers'); - $prop->setAccessible(true); /** @var array $headers */ $headers = $prop->getValue($plugin); @@ -176,7 +173,6 @@ public function testUserAgentVersionDoesNotContainBuildMetadataSuffix(): void { $method = (new \ReflectionClass(DockerContainerClient::class)) ->getMethod('resolveVersion'); - $method->setAccessible(true); /** @var string $version */ $version = $method->invoke(null); @@ -197,7 +193,6 @@ public function testOutOfBoundsExceptionFallsBackToUnknown(): void // unhandled OutOfBoundsException and the test fails — it is not self-testing. $method = (new \ReflectionClass(DockerContainerClient::class)) ->getMethod('resolveVersion'); - $method->setAccessible(true); /** @var string $result */ $result = $method->invoke(null, 'testcontainers/this-package-does-not-exist'); diff --git a/tests/Unit/Utils/HostResolverTest.php b/tests/Unit/Utils/HostResolverTest.php index a92ed90..25e6966 100644 --- a/tests/Unit/Utils/HostResolverTest.php +++ b/tests/Unit/Utils/HostResolverTest.php @@ -92,7 +92,7 @@ public function testReturnsHostFromGatewayWhenRunningInContainer(): void // Build a fake network inspection response: $fakeConfig = new class () { - public function getGateway(): ?string + public function getGateway(): string { return '172.0.0.1'; } @@ -214,7 +214,7 @@ protected function findGateway(string $networkName): ?string { return null; } - protected function findDefaultGateway(): ?string + protected function findDefaultGateway(): string { return '172.0.0.2'; } From 969bb44ab0179a2c1f54605ad129d95e891eac67 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Mon, 14 Sep 2026 14:43:14 +0200 Subject: [PATCH 2/2] chore: require docker-php >=1.45.7 and docker-php-api >=7.1.45.5 Older releases ship the previous EndpointSettings constructor and nullable FETCH_RESPONSE return types the code no longer targets. Co-Authored-By: Claude Fable 5.1 --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cfc6255..7e12d8b 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "require": { "ext-curl": "*", "php": ">= 8.1", - "beluga-php/docker-php": "^1.45", + "beluga-php/docker-php": "^1.45.7", + "beluga-php/docker-php-api": "^7.1.45.5", "php-http/client-common": "^2.7" }, "require-dev": {