Make WordPress Core


Ignore:
Timestamp:
08/02/2023 06:35:24 PM (3 years ago)
Author:
hellofromTonya
Message:

Code Modernization: Deprecate dynamic properties in WP_List_Table magic methods.

The unknown use of unknown dynamic property within the WP_List_Table property magic methods is now deprecated. A descriptive deprecation notice is provided to alert developers to declare the property on the child class extending WP_List_Table.

Changes in this commit:

  • Adds a deprecation notice to the __get(), __set(), __isset(), __unset() magic methods, i.e. to alert and inform developers when attempting to get/set/isset/unset a dynamic property.
  • Fixes __get() to explicitly returns null when attempting to get a dynamic property.
  • Removes returning the value when setting a declared property, as (a) unnecessary and (b) __set() should return void per the PHP handbook.
  • Adds unit tests for happy and unhappy paths.

For backward compatibility, no changes are made to the internal declared properties listed in $compat_fields and accessed through the magic methods.

For example:
A child class uses a property named $data that is not declared / defined as a property on the child class. When getting its value, e.g. $list_table->data, the WP_List_Table::__get() magic method is invoked, the following deprecation notice thrown, and null returned:

The property data is not defined. Setting a dynamic (undefined) property is deprecated since version 6.4.0! Instead, define the property on the class.

Why not remove the magic methods, remove the $compat_fields property, and restore the properties public?

tl;dr Backward compatibility.

Several plugins, one of which has over 5M installs, add a property to the $compat_fields array. Removing the property would cause an Undefined property Warning (PHP 8) | Notice (PHP 7) to be thrown. Removing the associated code would change the functionality.

Why not limit the deprecation for PHP versions >= 8.2?

tl;dr original design intent and inform

The magic methods and $compat_fields property were added for one purpose: to continue providing external access to internal properties declared on WP_List_Table. They were not intended to be used for dynamic properties.

Deprecating that unintended usage both alerts developers a change is needed in their child class and informs them what to change.

References:

Related to #14579, #22234, #30891.

Follow-up to [15491], [28493], [28521], [28524], [31146].

Props antonvlasenko, jrf, markjaquith, hellofromTonya, SergeyBiryukov, desrosj, peterwilsoncc, audrasjb, costdev, oglekler, jeffpaul.
Fixes #58896.
See #56034.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/admin/wpListTable.php

    r56348 r56349  
    362362                );
    363363        }
     364
     365        /**
     366         * @dataProvider data_compat_fields
     367         * @ticket 58896
     368         *
     369         * @covers WP_List_Table::__get()
     370         *
     371         * @param string $property_name Property name to get.
     372         * @param mixed $expected       Expected value.
     373         */
     374        public function test_should_get_compat_fields_defined_property( $property_name, $expected ) {
     375                $list_table = new WP_List_Table( array( 'plural' => '_wp_tests__get' ) );
     376
     377                if ( 'screen' === $property_name ) {
     378                        $this->assertInstanceOf( $expected, $list_table->$property_name );
     379                } else {
     380                        $this->assertSame( $expected, $list_table->$property_name );
     381                }
     382        }
     383
     384        /**
     385         * @ticket 58896
     386         *
     387         * @covers WP_List_Table::__get()
     388         */
     389        public function test_should_throw_deprecation_when_getting_dynamic_property() {
     390                $this->expectDeprecation();
     391                $this->expectDeprecationMessage(
     392                        'The property `undefined_property` is not defined. Getting a dynamic (undefined) property is ' .
     393                        'deprecated since version 6.4.0! Instead, define the property on the class.'
     394                );
     395                $this->assertNull( $this->list_table->undefined_property, 'Getting a dynamic property should return null from WP_List_Table::__get()' );
     396        }
     397
     398        /**
     399         * @dataProvider data_compat_fields
     400         * @ticket 58896
     401         *
     402         * @covers WP_List_Table::__set()
     403         *
     404         * @param string $property_name Property name to set.
     405         */
     406        public function test_should_set_compat_fields_defined_property( $property_name ) {
     407                $value                            = uniqid();
     408                $this->list_table->$property_name = $value;
     409
     410                $this->assertSame( $value, $this->list_table->$property_name );
     411        }
     412
     413        /**
     414         * @ticket 58896
     415         *
     416         * @covers WP_List_Table::__set()
     417         */
     418        public function test_should_throw_deprecation_when_setting_dynamic_property() {
     419                $this->expectDeprecation();
     420                $this->expectDeprecationMessage(
     421                        'The property `undefined_property` is not defined. Setting a dynamic (undefined) property is ' .
     422                        'deprecated since version 6.4.0! Instead, define the property on the class.'
     423                );
     424                $this->list_table->undefined_property = 'some value';
     425        }
     426
     427        /**
     428         * @dataProvider data_compat_fields
     429         * @ticket 58896
     430         *
     431         * @covers WP_List_Table::__isset()
     432         *
     433         * @param string $property_name Property name to check.
     434         * @param mixed $expected       Expected value.
     435         */
     436        public function test_should_isset_compat_fields_defined_property( $property_name, $expected ) {
     437                $actual = isset( $this->list_table->$property_name );
     438                if ( is_null( $expected ) ) {
     439                        $this->assertFalse( $actual );
     440                } else {
     441                        $this->assertTrue( $actual );
     442                }
     443        }
     444
     445        /**
     446         * @ticket 58896
     447         *
     448         * @covers WP_List_Table::__isset()
     449         */
     450        public function test_should_throw_deprecation_when_isset_of_dynamic_property() {
     451                $this->expectDeprecation();
     452                $this->expectDeprecationMessage(
     453                        'The property `undefined_property` is not defined. Checking `isset()` on a dynamic (undefined) property ' .
     454                        'is deprecated since version 6.4.0! Instead, define the property on the class.'
     455                );
     456                $this->assertFalse( isset( $this->list_table->undefined_property ), 'Checking a dyanmic property should return false from WP_List_Table::__isset()' );
     457        }
     458
     459        /**
     460         * @dataProvider data_compat_fields
     461         * @ticket 58896
     462         *
     463         * @covers WP_List_Table::__unset()
     464         *
     465         * @param string $property_name Property name to unset.
     466         */
     467        public function test_should_unset_compat_fields_defined_property( $property_name ) {
     468                unset( $this->list_table->$property_name );
     469                $this->assertFalse( isset( $this->list_table->$property_name ) );
     470        }
     471
     472        /**
     473         * @ticket 58896
     474         *
     475         * @covers WP_List_Table::__unset()
     476         */
     477        public function test_should_throw_deprecation_when_unset_of_dynamic_property() {
     478                $this->expectDeprecation();
     479                $this->expectDeprecationMessage(
     480                        'A property `undefined_property` is not defined. Unsetting a dynamic (undefined) property is ' .
     481                        'deprecated since version 6.4.0! Instead, define the property on the class.'
     482                );
     483                unset( $this->list_table->undefined_property );
     484        }
     485
     486        /**
     487         * Data provider.
     488         *
     489         * @return array
     490         */
     491        public function data_compat_fields() {
     492                return array(
     493                        '_args'            => array(
     494                                'property_name' => '_args',
     495                                'expected'      => array(
     496                                        'plural'   => '_wp_tests__get',
     497                                        'singular' => '',
     498                                        'ajax'     => false,
     499                                        'screen'   => null,
     500                                ),
     501                        ),
     502                        '_pagination_args' => array(
     503                                'property_name' => '_pagination_args',
     504                                'expected'      => array(),
     505                        ),
     506                        'screen'           => array(
     507                                'property_name' => 'screen',
     508                                'expected'      => WP_Screen::class,
     509                        ),
     510                        '_actions'         => array(
     511                                'property_name' => '_actions',
     512                                'expected'      => null,
     513                        ),
     514                        '_pagination'      => array(
     515                                'property_name' => '_pagination',
     516                                'expected'      => null,
     517                        ),
     518                );
     519        }
    364520}
Note: See TracChangeset for help on using the changeset viewer.