From 2ece6633939c7d776421c67ba396d461ebd545a0 Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Sun, 2 Nov 2025 20:17:46 +0000 Subject: [PATCH 1/4] Add PHP 8.5 to CI --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e7fd8a78e..b328e2352 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,6 +19,9 @@ jobs: include: - php: 8.2 analysis: true + - php: 8.5 + experimental: true + composer-options: '--ignore-platform-req=php+' - php: nightly experimental: true composer-options: '--ignore-platform-req=php+' From f0f58008a122c1f11b1a7d0fc6742e70f3dc50e0 Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Sun, 2 Nov 2025 20:42:25 +0000 Subject: [PATCH 2/4] Only call setAccessible() if PHP < 8.1 --- tests/AppTest.php | 18 ++++---- tests/Error/AbstractErrorRendererTest.php | 8 ++-- tests/Factory/AppFactoryTest.php | 4 +- .../SlimHttpServerRequestCreatorTest.php | 4 +- tests/Handlers/ErrorHandlerTest.php | 44 +++++++++---------- .../OutputBufferingMiddlewareTest.php | 4 +- tests/ResponseEmitterTest.php | 2 +- tests/Routing/DispatcherTest.php | 8 ++-- tests/TestCase.php | 13 ++++++ 9 files changed, 59 insertions(+), 46 deletions(-) diff --git a/tests/AppTest.php b/tests/AppTest.php index 1724695d6..f691de12b 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -703,18 +703,18 @@ public function testAddRoutingMiddleware(): void // Check that the routing middleware really has been added to the tip of the app middleware stack. $middlewareDispatcherProperty = new ReflectionProperty(App::class, 'middlewareDispatcher'); - $middlewareDispatcherProperty->setAccessible(true); + $this->setAccessible($middlewareDispatcherProperty); /** @var MiddlewareDispatcher $middlewareDispatcher */ $middlewareDispatcher = $middlewareDispatcherProperty->getValue($app); $tipProperty = new ReflectionProperty(MiddlewareDispatcher::class, 'tip'); - $tipProperty->setAccessible(true); + $this->setAccessible($tipProperty); /** @var RequestHandlerInterface $tip */ $tip = $tipProperty->getValue($middlewareDispatcher); $reflection = new ReflectionClass($tip); $middlewareProperty = $reflection->getProperty('middleware'); - $middlewareProperty->setAccessible(true); + $this->setAccessible($middlewareProperty); $this->assertSame($routingMiddleware, $middlewareProperty->getValue($tip)); $this->assertInstanceOf(RoutingMiddleware::class, $routingMiddleware); @@ -736,18 +736,18 @@ public function testAddErrorMiddleware(): void // Check that the error middleware really has been added to the tip of the app middleware stack. $middlewareDispatcherProperty = new ReflectionProperty(App::class, 'middlewareDispatcher'); - $middlewareDispatcherProperty->setAccessible(true); + $this->setAccessible($middlewareDispatcherProperty); /** @var MiddlewareDispatcher $middlewareDispatcher */ $middlewareDispatcher = $middlewareDispatcherProperty->getValue($app); $tipProperty = new ReflectionProperty(MiddlewareDispatcher::class, 'tip'); - $tipProperty->setAccessible(true); + $this->setAccessible($tipProperty); /** @var RequestHandlerInterface $tip */ $tip = $tipProperty->getValue($middlewareDispatcher); $reflection = new ReflectionClass($tip); $middlewareProperty = $reflection->getProperty('middleware'); - $middlewareProperty->setAccessible(true); + $this->setAccessible($middlewareProperty); $this->assertSame($errorMiddleware, $middlewareProperty->getValue($tip)); $this->assertInstanceOf(ErrorMiddleware::class, $errorMiddleware); @@ -766,18 +766,18 @@ public function testAddBodyParsingMiddleware(): void // Check that the body parsing middleware really has been added to the tip of the app middleware stack. $middlewareDispatcherProperty = new ReflectionProperty(App::class, 'middlewareDispatcher'); - $middlewareDispatcherProperty->setAccessible(true); + $this->setAccessible($middlewareDispatcherProperty); /** @var MiddlewareDispatcher $middlewareDispatcher */ $middlewareDispatcher = $middlewareDispatcherProperty->getValue($app); $tipProperty = new ReflectionProperty(MiddlewareDispatcher::class, 'tip'); - $tipProperty->setAccessible(true); + $this->setAccessible($tipProperty); /** @var RequestHandlerInterface $tip */ $tip = $tipProperty->getValue($middlewareDispatcher); $reflection = new ReflectionClass($tip); $middlewareProperty = $reflection->getProperty('middleware'); - $middlewareProperty->setAccessible(true); + $this->setAccessible($middlewareProperty); $this->assertSame($bodyParsingMiddleware, $middlewareProperty->getValue($tip)); $this->assertInstanceOf(BodyParsingMiddleware::class, $bodyParsingMiddleware); diff --git a/tests/Error/AbstractErrorRendererTest.php b/tests/Error/AbstractErrorRendererTest.php index 88cfa135b..4b7237c62 100644 --- a/tests/Error/AbstractErrorRendererTest.php +++ b/tests/Error/AbstractErrorRendererTest.php @@ -60,7 +60,7 @@ public function testHTMLErrorRendererRenderFragmentMethod() $reflectionRenderer = new ReflectionClass(HtmlErrorRenderer::class); $method = $reflectionRenderer->getMethod('renderExceptionFragment'); - $method->setAccessible(true); + $this->setAccessible($method); $output = $method->invoke($renderer, $exception); $this->assertMatchesRegularExpression('/.*Type:*/', $output); @@ -101,7 +101,7 @@ public function testJSONErrorRendererDisplaysErrorDetails() $reflectionRenderer = new ReflectionClass(JsonErrorRenderer::class); $method = $reflectionRenderer->getMethod('formatExceptionFragment'); - $method->setAccessible(true); + $this->setAccessible($method); $fragment = $method->invoke($renderer, $exception); $output = json_encode(json_decode($renderer->__invoke($exception, true))); @@ -128,7 +128,7 @@ public function testJSONErrorRendererDisplaysPreviousError() $renderer = new JsonErrorRenderer(); $reflectionRenderer = new ReflectionClass(JsonErrorRenderer::class); $method = $reflectionRenderer->getMethod('formatExceptionFragment'); - $method->setAccessible(true); + $this->setAccessible($method); $output = json_encode(json_decode($renderer->__invoke($exception, true))); @@ -208,7 +208,7 @@ public function testPlainTextErrorRendererFormatFragmentMethod() $reflectionRenderer = new ReflectionClass(PlainTextErrorRenderer::class); $method = $reflectionRenderer->getMethod('formatExceptionFragment'); - $method->setAccessible(true); + $this->setAccessible($method); $output = $method->invoke($renderer, $exception); $this->assertIsString($output); diff --git a/tests/Factory/AppFactoryTest.php b/tests/Factory/AppFactoryTest.php index 700c85ebf..42565e89f 100644 --- a/tests/Factory/AppFactoryTest.php +++ b/tests/Factory/AppFactoryTest.php @@ -77,7 +77,7 @@ public function testCreateAppWithAllImplementations(string $psr17factory, string $routeCollector = $app->getRouteCollector(); $responseFactoryProperty = new ReflectionProperty(RouteCollector::class, 'responseFactory'); - $responseFactoryProperty->setAccessible(true); + $this->setAccessible($responseFactoryProperty); $responseFactory = $responseFactoryProperty->getValue($routeCollector); @@ -281,7 +281,7 @@ public function testResponseAndStreamFactoryIsBeingInjectedInDecoratedResponseFa $response = $responseFactory->createResponse(); $streamFactoryProperty = new ReflectionProperty(DecoratedResponse::class, 'streamFactory'); - $streamFactoryProperty->setAccessible(true); + $this->setAccessible($streamFactoryProperty); $this->assertSame($streamFactoryProphecy->reveal(), $streamFactoryProperty->getValue($response)); } diff --git a/tests/Factory/Psr17/SlimHttpServerRequestCreatorTest.php b/tests/Factory/Psr17/SlimHttpServerRequestCreatorTest.php index 328f4cb4c..6bca114c8 100644 --- a/tests/Factory/Psr17/SlimHttpServerRequestCreatorTest.php +++ b/tests/Factory/Psr17/SlimHttpServerRequestCreatorTest.php @@ -36,7 +36,7 @@ public function tearDown(): void SlimHttpServerRequestCreator::class, 'serverRequestDecoratorClass' ); - $serverRequestDecoratorClassProperty->setAccessible(true); + $this->setAccessible($serverRequestDecoratorClassProperty); $serverRequestDecoratorClassProperty->setValue($slimHttpServerRequestCreator, ServerRequest::class); } @@ -69,7 +69,7 @@ public function testCreateServerRequestFromGlobalsThrowsRuntimeException() SlimHttpServerRequestCreator::class, 'serverRequestDecoratorClass' ); - $serverRequestDecoratorClassProperty->setAccessible(true); + $this->setAccessible($serverRequestDecoratorClassProperty); $serverRequestDecoratorClassProperty->setValue($slimHttpServerRequestCreator, ''); $slimHttpServerRequestCreator->createServerRequestFromGlobals(); diff --git a/tests/Handlers/ErrorHandlerTest.php b/tests/Handlers/ErrorHandlerTest.php index 296e38b8a..286accf3a 100644 --- a/tests/Handlers/ErrorHandlerTest.php +++ b/tests/Handlers/ErrorHandlerTest.php @@ -40,15 +40,15 @@ public function testDetermineRenderer() $class = new ReflectionClass(ErrorHandler::class); $callableResolverProperty = $class->getProperty('callableResolver'); - $callableResolverProperty->setAccessible(true); + $this->setAccessible($callableResolverProperty); $callableResolverProperty->setValue($handler, $this->getCallableResolver()); $reflectionProperty = $class->getProperty('contentType'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $reflectionProperty->setValue($handler, 'application/json'); $method = $class->getMethod('determineRenderer'); - $method->setAccessible(true); + $this->setAccessible($method); $renderer = $method->invoke($handler); $this->assertIsCallable($renderer); @@ -78,15 +78,15 @@ public function testDetermineStatusCode() $class = new ReflectionClass(ErrorHandler::class); $reflectionProperty = $class->getProperty('responseFactory'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $reflectionProperty->setValue($handler, $this->getResponseFactory()); $reflectionProperty = $class->getProperty('exception'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $reflectionProperty->setValue($handler, new HttpNotFoundException($request)); $method = $class->getMethod('determineStatusCode'); - $method->setAccessible(true); + $this->setAccessible($method); $statusCode = $method->invoke($handler); $this->assertSame($statusCode, 404); @@ -133,15 +133,15 @@ public function testHalfValidContentType() $class = new ReflectionClass(ErrorHandler::class); $reflectionProperty = $class->getProperty('responseFactory'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $reflectionProperty->setValue($handler, $this->getResponseFactory()); $reflectionProperty = $class->getProperty('errorRenderers'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $reflectionProperty->setValue($handler, $newErrorRenderers); $method = $class->getMethod('determineContentType'); - $method->setAccessible(true); + $this->setAccessible($method); $contentType = $method->invoke($handler, $request); @@ -165,15 +165,15 @@ public function testDetermineContentTypeTextPlainMultiAcceptHeader() $class = new ReflectionClass(ErrorHandler::class); $reflectionProperty = $class->getProperty('responseFactory'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $reflectionProperty->setValue($handler, $this->getResponseFactory()); $reflectionProperty = $class->getProperty('errorRenderers'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $reflectionProperty->setValue($handler, $errorRenderers); $method = $class->getMethod('determineContentType'); - $method->setAccessible(true); + $this->setAccessible($method); $contentType = $method->invoke($handler, $request); @@ -196,15 +196,15 @@ public function testDetermineContentTypeApplicationJsonOrXml() $class = new ReflectionClass(ErrorHandler::class); $reflectionProperty = $class->getProperty('responseFactory'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $reflectionProperty->setValue($handler, $this->getResponseFactory()); $reflectionProperty = $class->getProperty('errorRenderers'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $reflectionProperty->setValue($handler, $errorRenderers); $method = $class->getMethod('determineContentType'); - $method->setAccessible(true); + $this->setAccessible($method); $contentType = $method->invoke($handler, $request); @@ -224,7 +224,7 @@ public function testAcceptableMediaTypeIsNotFirstInList() // provide access to the determineContentType() as it's a protected method $class = new ReflectionClass(ErrorHandler::class); $method = $class->getMethod('determineContentType'); - $method->setAccessible(true); + $this->setAccessible($method); // use a mock object here as ErrorHandler cannot be directly instantiated $handler = $this->createMock(ErrorHandler::class); @@ -242,7 +242,7 @@ public function testRegisterErrorRenderer() $reflectionClass = new ReflectionClass(ErrorHandler::class); $reflectionProperty = $reflectionClass->getProperty('errorRenderers'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $errorRenderers = $reflectionProperty->getValue($handler); $this->assertArrayHasKey('application/slim', $errorRenderers); @@ -255,11 +255,11 @@ public function testSetDefaultErrorRenderer() $reflectionClass = new ReflectionClass(ErrorHandler::class); $reflectionProperty = $reflectionClass->getProperty('defaultErrorRenderer'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $defaultErrorRenderer = $reflectionProperty->getValue($handler); $defaultErrorRendererContentTypeProperty = $reflectionClass->getProperty('defaultErrorRendererContentType'); - $defaultErrorRendererContentTypeProperty->setAccessible(true); + $this->setAccessible($defaultErrorRendererContentTypeProperty); $defaultErrorRendererContentType = $defaultErrorRendererContentTypeProperty->getValue($handler); $this->assertSame(PlainTextErrorRenderer::class, $defaultErrorRenderer); @@ -396,16 +396,16 @@ public function testLogErrorRenderer() $handler->setLogErrorRenderer('logErrorRenderer'); $displayErrorDetailsProperty = new ReflectionProperty($handler, 'displayErrorDetails'); - $displayErrorDetailsProperty->setAccessible(true); + $this->setAccessible($displayErrorDetailsProperty); $displayErrorDetailsProperty->setValue($handler, true); $exception = new RuntimeException(); $exceptionProperty = new ReflectionProperty($handler, 'exception'); - $exceptionProperty->setAccessible(true); + $this->setAccessible($exceptionProperty); $exceptionProperty->setValue($handler, $exception); $writeToErrorLogMethod = new ReflectionMethod($handler, 'writeToErrorLog'); - $writeToErrorLogMethod->setAccessible(true); + $this->setAccessible($writeToErrorLogMethod); $writeToErrorLogMethod->invoke($handler); } } diff --git a/tests/Middleware/OutputBufferingMiddlewareTest.php b/tests/Middleware/OutputBufferingMiddlewareTest.php index aa84dd019..7c7469e24 100644 --- a/tests/Middleware/OutputBufferingMiddlewareTest.php +++ b/tests/Middleware/OutputBufferingMiddlewareTest.php @@ -26,7 +26,7 @@ public function testStyleDefaultValid() $middleware = new OutputBufferingMiddleware($this->getStreamFactory()); $reflectionProperty = new ReflectionProperty($middleware, 'style'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $value = $reflectionProperty->getValue($middleware); $this->assertSame('append', $value); @@ -37,7 +37,7 @@ public function testStyleCustomValid() $middleware = new OutputBufferingMiddleware($this->getStreamFactory(), 'prepend'); $reflectionProperty = new ReflectionProperty($middleware, 'style'); - $reflectionProperty->setAccessible(true); + $this->setAccessible($reflectionProperty); $value = $reflectionProperty->getValue($middleware); $this->assertSame('prepend', $value); diff --git a/tests/ResponseEmitterTest.php b/tests/ResponseEmitterTest.php index b0fadb2a1..f5b5a9d43 100644 --- a/tests/ResponseEmitterTest.php +++ b/tests/ResponseEmitterTest.php @@ -280,7 +280,7 @@ public function testWillHandleInvalidConnectionStatusWithAnIndeterminateBody(): $mirror = new ReflectionClass(ResponseEmitter::class); $emitBodyMethod = $mirror->getMethod('emitBody'); - $emitBodyMethod->setAccessible(true); + $this->setAccessible($emitBodyMethod); $emitBodyMethod->invoke($responseEmitter, $response); $this->expectOutputString(""); diff --git a/tests/Routing/DispatcherTest.php b/tests/Routing/DispatcherTest.php index cefd15192..49abd6090 100644 --- a/tests/Routing/DispatcherTest.php +++ b/tests/Routing/DispatcherTest.php @@ -34,7 +34,7 @@ public function testCreateDispatcher() $dispatcher = new Dispatcher($routeCollector); $method = new ReflectionMethod(Dispatcher::class, 'createDispatcher'); - $method->setAccessible(true); + $this->setAccessible($method); $this->assertInstanceOf(FastRouteDispatcher::class, $method->invoke($dispatcher)); } @@ -57,7 +57,7 @@ public function testRouteCacheFileCanBeDispatched() $routeCollector->setCacheFile($cacheFile); $method = new ReflectionMethod(Dispatcher::class, 'createDispatcher'); - $method->setAccessible(true); + $this->setAccessible($method); $method->invoke($dispatcher); $this->assertFileExists($cacheFile, 'cache file was not created'); @@ -66,7 +66,7 @@ public function testRouteCacheFileCanBeDispatched() $dispatcher2 = new Dispatcher($routeCollector2); $method = new ReflectionMethod(Dispatcher::class, 'createDispatcher'); - $method->setAccessible(true); + $this->setAccessible($method); $method->invoke($dispatcher2); /** @var RoutingResults $result */ @@ -88,7 +88,7 @@ public function testCreateDispatcherReturnsSameDispatcherASecondTime() $dispatcher = new Dispatcher($routeCollector); $method = new ReflectionMethod(Dispatcher::class, 'createDispatcher'); - $method->setAccessible(true); + $this->setAccessible($method); $fastRouteDispatcher = $method->invoke($dispatcher); $fastRouteDispatcher2 = $method->invoke($dispatcher); diff --git a/tests/TestCase.php b/tests/TestCase.php index aef12efae..5c9d75ee2 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -20,6 +20,8 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Server\RequestHandlerInterface; +use ReflectionMethod; +use ReflectionProperty; use Slim\CallableResolver; use Slim\Interfaces\CallableResolverInterface; use Slim\MiddlewareDispatcher; @@ -120,4 +122,15 @@ protected function createStream(string $contents = ''): StreamInterface $psr7ObjectProvider = new PSR7ObjectProvider(); return $psr7ObjectProvider->createStream($contents); } + + /** + * @param ReflectionProperty|ReflectionMethod $ref + * @return void + */ + protected function setAccessible($ref, bool $accessible = true): void + { + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible($accessible); + } + } } From c9240bff5229fea86400d86b71fe2a384e03c34e Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Sun, 2 Nov 2025 21:08:38 +0000 Subject: [PATCH 3/4] Fix risky test We need to mock out the LoggerInterface's error call so that it doesn't call error_log() behind the scenes on us as this will output a new line character and cause a risky test notice. --- tests/Handlers/ErrorHandlerTest.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/Handlers/ErrorHandlerTest.php b/tests/Handlers/ErrorHandlerTest.php index 286accf3a..1cd6bcc89 100644 --- a/tests/Handlers/ErrorHandlerTest.php +++ b/tests/Handlers/ErrorHandlerTest.php @@ -10,6 +10,7 @@ namespace Slim\Tests\Handlers; +use Prophecy\Argument; use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; use ReflectionClass; @@ -392,7 +393,16 @@ public function testLogErrorRenderer() ->willReturn($renderer) ->shouldBeCalledOnce(); - $handler = new ErrorHandler($callableResolverProphecy->reveal(), $this->getResponseFactory()); + $loggerProphecy = $this->prophesize(LoggerInterface::class); + $loggerProphecy + ->error(Argument::type('string')) + ->shouldBeCalled(); + + $handler = new ErrorHandler( + $callableResolverProphecy->reveal(), + $this->getResponseFactory(), + $loggerProphecy->reveal() + ); $handler->setLogErrorRenderer('logErrorRenderer'); $displayErrorDetailsProperty = new ReflectionProperty($handler, 'displayErrorDetails'); From e1fa9aa7fb69c4c5a9bb23be80407a55b2f3f792 Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Sun, 2 Nov 2025 21:12:41 +0000 Subject: [PATCH 4/4] Add PHP 8.5 to composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index aa966cab3..5fc397098 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "wiki": "https://github.com/slimphp/Slim/wiki" }, "require": { - "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "ext-json": "*", "nikic/fast-route": "^1.3", "psr/container": "^1.0 || ^2.0",