From 1f59d8f96ae4d59567e37b33cc2443ac46a3bf68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Perona?= Date: Thu, 30 Jul 2026 17:55:09 -0400 Subject: [PATCH 1/2] Fix PHP 8.3/8.5 deprecations in TestCaseTrait reflection helpers ReflectionProperty::setValue() emits a deprecation as of PHP 8.3 when its first argument is neither null nor an object, which happened whenever set_reflective_property() targeted a static property (the caller passes a class name). Static properties are now set with an explicit null target. Reflection*::setAccessible() has no effect since PHP 8.1 and is deprecated as of PHP 8.5. The calls are now routed through a helper that skips them on PHP 8.1+, keeping them for the still-supported PHP 7.4 and 8.0. Adds a regression test covering both helpers plus a deprecation-free assertion. Verified on PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5. Fixes #29 Co-Authored-By: Claude Opus 5 --- TestCaseTrait.php | 39 +++++++++-- Tests/Unit/testTestCaseTrait.php | 112 +++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 Tests/Unit/testTestCaseTrait.php diff --git a/TestCaseTrait.php b/TestCaseTrait.php index 50608d6..25e423c 100644 --- a/TestCaseTrait.php +++ b/TestCaseTrait.php @@ -47,7 +47,8 @@ protected function getTestData( $dir, $filename ) { protected function get_reflective_method( $method_name, $class_name ) { $class = new ReflectionClass( $class_name ); $method = $class->getMethod( $method_name ); - $method->setAccessible( true ); + + self::set_reflector_accessible( $method, true ); return $method; } @@ -64,7 +65,8 @@ protected function get_reflective_method( $method_name, $class_name ) { protected function get_reflective_property( $property, $class ) { $class = new ReflectionClass( $class ); $property = $class->getProperty( $property ); - $property->setAccessible( true ); + + self::set_reflector_accessible( $property, true ); return $property; } @@ -81,8 +83,15 @@ protected function get_reflective_property( $property, $class ) { */ protected function set_reflective_property( $value, $property, $instance ) { $property = $this->get_reflective_property( $property, $instance ); - $property->setValue( $instance, $value ); - $property->setAccessible( false ); + + // A static property has no target object: passing anything but null is deprecated as of PHP 8.3. + if ( $property->isStatic() ) { + $property->setValue( null, $value ); + } else { + $property->setValue( $instance, $value ); + } + + self::set_reflector_accessible( $property, false ); return $property; } @@ -90,13 +99,33 @@ protected function set_reflective_property( $value, $property, $instance ) { protected function getNonPublicPropertyValue( $property, $class, $instance = null ) { $property = $this->get_reflective_property( $property, $class ); - if ( is_null( $instance ) ) { + if ( is_null( $instance ) || $property->isStatic() ) { return $property->getValue(); } return $property->getValue( $instance ); } + /** + * Toggles accessibility on a reflected method or property. + * + * ReflectionMethod::setAccessible() and ReflectionProperty::setAccessible() have no effect since PHP 8.1, where + * reflection grants access to non-public members by default, and are deprecated as of PHP 8.5. They are still + * required on PHP 7.4 and 8.0. + * + * @param ReflectionMethod|ReflectionProperty $reflector Reflected method or property. + * @param bool $accessible Whether to make the member accessible. + * + * @return void + */ + private static function set_reflector_accessible( $reflector, $accessible ) { + if ( PHP_VERSION_ID >= 80100 ) { + return; + } + + $reflector->setAccessible( $accessible ); + } + /** * Format the HTML by stripping out the whitespace between the HTML tags and then putting each tag on a separate * line. diff --git a/Tests/Unit/testTestCaseTrait.php b/Tests/Unit/testTestCaseTrait.php new file mode 100644 index 0000000..f35c72e --- /dev/null +++ b/Tests/Unit/testTestCaseTrait.php @@ -0,0 +1,112 @@ +instance_property; + } + + public static function get_static_property() { + return self::$static_property; + } + + public static function reset() { + self::$static_property = 'initial static'; + } +} + +/** + * @covers WPMedia\PHPUnit\TestCaseTrait::get_reflective_method + * @covers WPMedia\PHPUnit\TestCaseTrait::get_reflective_property + * @covers WPMedia\PHPUnit\TestCaseTrait::set_reflective_property + * @covers WPMedia\PHPUnit\TestCaseTrait::getNonPublicPropertyValue + * @group TestCaseTrait + */ +class Test_TestCaseTrait extends TestCase { + + protected function tear_down() { + ReflectionTarget::reset(); + + parent::tear_down(); + } + + public function testShouldSetAndGetInstancePropertyWhenGivenAnInstance() { + $target = new ReflectionTarget(); + + $this->set_reflective_property( 'changed', 'instance_property', $target ); + + $this->assertSame( 'changed', $target->get_instance_property() ); + $this->assertSame( 'changed', $this->getNonPublicPropertyValue( 'instance_property', ReflectionTarget::class, $target ) ); + } + + public function testShouldSetAndGetStaticPropertyWhenGivenAClassName() { + $this->set_reflective_property( 'changed static', 'static_property', ReflectionTarget::class ); + + $this->assertSame( 'changed static', ReflectionTarget::get_static_property() ); + $this->assertSame( 'changed static', $this->getNonPublicPropertyValue( 'static_property', ReflectionTarget::class ) ); + } + + public function testShouldSetAndGetStaticPropertyWhenGivenAnInstance() { + $target = new ReflectionTarget(); + + $this->set_reflective_property( 'changed static', 'static_property', $target ); + + $this->assertSame( 'changed static', ReflectionTarget::get_static_property() ); + $this->assertSame( 'changed static', $this->getNonPublicPropertyValue( 'static_property', ReflectionTarget::class, $target ) ); + } + + public function testShouldInvokeNonPublicMethod() { + $method = $this->get_reflective_method( 'secret', ReflectionTarget::class ); + + $this->assertSame( 'secret value', $method->invoke( new ReflectionTarget(), 'value' ) ); + } + + /** + * The helpers must not emit deprecations on any supported PHP version: + * ReflectionProperty::setValue() with a non-object first argument is deprecated as of PHP 8.3, and + * Reflection*::setAccessible() is deprecated as of PHP 8.5. + * + * @see https://github.com/wp-media/phpunit/issues/29 + */ + public function testShouldNotTriggerDeprecationsOnAnySupportedPhpVersion() { + $deprecations = []; + + set_error_handler( + function ( $errno, $errstr ) use ( &$deprecations ) { + $deprecations[] = $errstr; + + return true; + }, + E_DEPRECATED | E_USER_DEPRECATED + ); + + try { + $target = new ReflectionTarget(); + + $this->get_reflective_method( 'secret', ReflectionTarget::class ); + $this->get_reflective_property( 'instance_property', $target ); + $this->set_reflective_property( 'changed', 'instance_property', $target ); + $this->set_reflective_property( 'changed static', 'static_property', ReflectionTarget::class ); + $this->getNonPublicPropertyValue( 'static_property', ReflectionTarget::class ); + } finally { + restore_error_handler(); + } + + $this->assertSame( [], $deprecations ); + } +} From a86c6c22e355b6c667c02ae623fe7ed2a35664c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Perona?= Date: Thu, 30 Jul 2026 18:00:07 -0400 Subject: [PATCH 2/2] Add PHP 8.5 to the test matrix Covers the version that surfaced the setAccessible() deprecation in #29, so the regression test in testTestCaseTrait.php guards it going forward. Co-Authored-By: Claude Opus 5 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70b53c4..a5ff12a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: operating-system: [ubuntu-latest] - php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4'] + php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] wp-versions: ['latest'] name: WP ${{ matrix.wp-versions }} with PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}.