diff --git a/src/wp-admin/includes/class-wp-list-table.php b/src/wp-admin/includes/class-wp-list-table.php index b8631d17a2d7b..c7c38a3561e75 100644 --- a/src/wp-admin/includes/class-wp-list-table.php +++ b/src/wp-admin/includes/class-wp-list-table.php @@ -176,6 +176,7 @@ public function __construct( $args = array() ) { * Makes private properties readable for backward compatibility. * * @since 4.0.0 + * @since 6.4.0 Getting a dynamic property is deprecated. * * @param string $name Property to get. * @return mixed Property. @@ -184,27 +185,42 @@ public function __get( $name ) { if ( in_array( $name, $this->compat_fields, true ) ) { return $this->$name; } + + trigger_error( + "The property `{$name}` is not defined. Getting a dynamic (undefined) property is " . + 'deprecated since version 6.4.0! Instead, define the property on the class.', + E_USER_DEPRECATED + ); + return null; } /** * Makes private properties settable for backward compatibility. * * @since 4.0.0 + * @since 6.4.0 Setting a dynamic property is deprecated. * * @param string $name Property to check if set. * @param mixed $value Property value. - * @return mixed Newly-set property. */ public function __set( $name, $value ) { if ( in_array( $name, $this->compat_fields, true ) ) { - return $this->$name = $value; + $this->$name = $value; + return; } + + trigger_error( + "The property `{$name}` is not defined. Setting a dynamic (undefined) property is " . + 'deprecated since version 6.4.0! Instead, define the property on the class.', + E_USER_DEPRECATED + ); } /** * Makes private properties checkable for backward compatibility. * * @since 4.0.0 + * @since 6.4.0 Checking a dynamic property is deprecated. * * @param string $name Property to check if set. * @return bool Whether the property is a back-compat property and it is set. @@ -214,6 +230,11 @@ public function __isset( $name ) { return isset( $this->$name ); } + trigger_error( + "The property `{$name}` is not defined. Checking `isset()` on a dynamic (undefined) property " . + 'is deprecated since version 6.4.0! Instead, define the property on the class.', + E_USER_DEPRECATED + ); return false; } @@ -221,13 +242,21 @@ public function __isset( $name ) { * Makes private properties un-settable for backward compatibility. * * @since 4.0.0 + * @since 6.4.0 Unsetting a dynamic property is deprecated. * * @param string $name Property to unset. */ public function __unset( $name ) { if ( in_array( $name, $this->compat_fields, true ) ) { unset( $this->$name ); + return; } + + trigger_error( + "A property `{$name}` is not defined. Unsetting a dynamic (undefined) property is " . + 'deprecated since version 6.4.0! Instead, define the property on the class.', + E_USER_DEPRECATED + ); } /** diff --git a/tests/phpunit/tests/admin/wpListTable.php b/tests/phpunit/tests/admin/wpListTable.php index b9ada05365b88..3b24cc5d90d89 100644 --- a/tests/phpunit/tests/admin/wpListTable.php +++ b/tests/phpunit/tests/admin/wpListTable.php @@ -361,4 +361,160 @@ public function data_get_views_links_doing_it_wrong() { ), ); } + + /** + * @dataProvider data_compat_fields + * @ticket 58896 + * + * @covers WP_List_Table::__get() + * + * @param string $property_name Property name to get. + * @param mixed $expected Expected value. + */ + public function test_should_get_compat_fields_defined_property( $property_name, $expected ) { + $list_table = new WP_List_Table( array( 'plural' => '_wp_tests__get' ) ); + + if ( 'screen' === $property_name ) { + $this->assertInstanceOf( $expected, $list_table->$property_name ); + } else { + $this->assertSame( $expected, $list_table->$property_name ); + } + } + + /** + * @ticket 58896 + * + * @covers WP_List_Table::__get() + */ + public function test_should_throw_deprecation_when_getting_dynamic_property() { + $this->expectDeprecation(); + $this->expectDeprecationMessage( + 'The property `undefined_property` is not defined. Getting a dynamic (undefined) property is ' . + 'deprecated since version 6.4.0! Instead, define the property on the class.' + ); + $this->assertNull( $this->list_table->undefined_property, 'Getting a dynamic property should return null from WP_List_Table::__get()' ); + } + + /** + * @dataProvider data_compat_fields + * @ticket 58896 + * + * @covers WP_List_Table::__set() + * + * @param string $property_name Property name to set. + */ + public function test_should_set_compat_fields_defined_property( $property_name ) { + $value = uniqid(); + $this->list_table->$property_name = $value; + + $this->assertSame( $value, $this->list_table->$property_name ); + } + + /** + * @ticket 58896 + * + * @covers WP_List_Table::__set() + */ + public function test_should_throw_deprecation_when_setting_dynamic_property() { + $this->expectDeprecation(); + $this->expectDeprecationMessage( + 'The property `undefined_property` is not defined. Setting a dynamic (undefined) property is ' . + 'deprecated since version 6.4.0! Instead, define the property on the class.' + ); + $this->list_table->undefined_property = 'some value'; + } + + /** + * @dataProvider data_compat_fields + * @ticket 58896 + * + * @covers WP_List_Table::__isset() + * + * @param string $property_name Property name to check. + * @param mixed $expected Expected value. + */ + public function test_should_isset_compat_fields_defined_property( $property_name, $expected ) { + $actual = isset( $this->list_table->$property_name ); + if ( is_null( $expected ) ) { + $this->assertFalse( $actual ); + } else { + $this->assertTrue( $actual ); + } + } + + /** + * @ticket 58896 + * + * @covers WP_List_Table::__isset() + */ + public function test_should_throw_deprecation_when_isset_of_dynamic_property() { + $this->expectDeprecation(); + $this->expectDeprecationMessage( + 'The property `undefined_property` is not defined. Checking `isset()` on a dynamic (undefined) property ' . + 'is deprecated since version 6.4.0! Instead, define the property on the class.' + ); + $this->assertFalse( isset( $this->list_table->undefined_property ), 'Checking a dyanmic property should return false from WP_List_Table::__isset()' ); + } + + /** + * @dataProvider data_compat_fields + * @ticket 58896 + * + * @covers WP_List_Table::__unset() + * + * @param string $property_name Property name to unset. + */ + public function test_should_unset_compat_fields_defined_property( $property_name ) { + unset( $this->list_table->$property_name ); + $this->assertFalse( isset( $this->list_table->$property_name ) ); + } + + /** + * @ticket 58896 + * + * @covers WP_List_Table::__unset() + */ + public function test_should_throw_deprecation_when_unset_of_dynamic_property() { + $this->expectDeprecation(); + $this->expectDeprecationMessage( + 'A property `undefined_property` is not defined. Unsetting a dynamic (undefined) property is ' . + 'deprecated since version 6.4.0! Instead, define the property on the class.' + ); + unset( $this->list_table->undefined_property ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_compat_fields() { + return array( + '_args' => array( + 'property_name' => '_args', + 'expected' => array( + 'plural' => '_wp_tests__get', + 'singular' => '', + 'ajax' => false, + 'screen' => null, + ), + ), + '_pagination_args' => array( + 'property_name' => '_pagination_args', + 'expected' => array(), + ), + 'screen' => array( + 'property_name' => 'screen', + 'expected' => WP_Screen::class, + ), + '_actions' => array( + 'property_name' => '_actions', + 'expected' => null, + ), + '_pagination' => array( + 'property_name' => '_pagination', + 'expected' => null, + ), + ); + } }