Make WordPress Core

Changeset 56353


Ignore:
Timestamp:
08/03/2023 04:25:25 PM (3 years ago)
Author:
hellofromTonya
Message:

Code Modernization: Deprecate dynamic properties in WP_User_Query magic methods.

The unknown use of unknown dynamic property within the WP_User_Query 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_User_Query.

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.
  • Fixes __set() by removing the value return after setting a declared property, as (a) unnecessary and (b) __set() should return void per the PHP handbook.
  • Fixes __isset() to return false if not in the $compat_fields, as isset() and __isset() should always return bool:
  • 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 as a property on the child class. When getting its value, e.g. $user_query->data, the WP_User_Query::__get() magic method is invoked, the following deprecation notice thrown, and null returned:

The property data is not declared. Setting a dynamic property is deprecated since version 6.4.0! Instead, declare 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.

If a plugin adds a property to $compat_fields array, then sites using that plugin would experience (a) an Undefined property Warning (PHP 8) | Notice (PHP 7) and (b) a possible change in behavior.

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_User_Query. 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, #27881, #30891.

Follow-up to [15491], [28528], [31144].

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

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-user-query.php

    r56169 r56353  
    11101110         *
    11111111         * @since 4.0.0
     1112         * @since 6.4.0 Getting a dynamic property is deprecated.
    11121113         *
    11131114         * @param string $name Property to get.
     
    11181119                        return $this->$name;
    11191120                }
     1121
     1122                trigger_error(
     1123                        "The property `{$name}` is not declared. Getting a dynamic property is " .
     1124                        'deprecated since version 6.4.0! Instead, declare the property on the class.',
     1125                        E_USER_DEPRECATED
     1126                );
     1127                return null;
    11201128        }
    11211129
     
    11241132         *
    11251133         * @since 4.0.0
     1134         * @since 6.4.0 Setting a dynamic property is deprecated.
    11261135         *
    11271136         * @param string $name  Property to check if set.
    11281137         * @param mixed  $value Property value.
    1129          * @return mixed Newly-set property.
    11301138         */
    11311139        public function __set( $name, $value ) {
    11321140                if ( in_array( $name, $this->compat_fields, true ) ) {
    1133                         return $this->$name = $value;
    1134                 }
     1141                        $this->$name = $value;
     1142                        return;
     1143                }
     1144
     1145                trigger_error(
     1146                        "The property `{$name}` is not declared. Setting a dynamic property is " .
     1147                        'deprecated since version 6.4.0! Instead, declare the property on the class.',
     1148                        E_USER_DEPRECATED
     1149                );
    11351150        }
    11361151
     
    11391154         *
    11401155         * @since 4.0.0
     1156         * @since 6.4.0 Checking a dynamic property is deprecated.
    11411157         *
    11421158         * @param string $name Property to check if set.
     
    11471163                        return isset( $this->$name );
    11481164                }
     1165
     1166                trigger_error(
     1167                        "The property `{$name}` is not declared. Checking `isset()` on a dynamic property " .
     1168                        'is deprecated since version 6.4.0! Instead, declare the property on the class.',
     1169                        E_USER_DEPRECATED
     1170                );
     1171                return false;
    11491172        }
    11501173
     
    11531176         *
    11541177         * @since 4.0.0
     1178         * @since 6.4.0 Unsetting a dynamic property is deprecated.
    11551179         *
    11561180         * @param string $name Property to unset.
     
    11591183                if ( in_array( $name, $this->compat_fields, true ) ) {
    11601184                        unset( $this->$name );
    1161                 }
     1185                        return;
     1186                }
     1187
     1188                trigger_error(
     1189                        "A property `{$name}` is not declared. Unsetting a dynamic property is " .
     1190                        'deprecated since version 6.4.0! Instead, declare the property on the class.',
     1191                        E_USER_DEPRECATED
     1192                );
    11621193        }
    11631194
  • trunk/tests/phpunit/tests/user/query.php

    r55745 r56353  
    22282228                $this->assertNotFalse( DateTime::createFromFormat( 'Y-m-d H:i:s', $results[0] ) );
    22292229        }
     2230
     2231        /**
     2232         * @dataProvider data_compat_fields
     2233         * @ticket 58897
     2234         *
     2235         * @covers WP_User_Query::__get()
     2236         *
     2237         * @param string $property_name Property name to get.
     2238         * @param mixed $expected       Expected value.
     2239         */
     2240        public function test_should_get_compat_fields( $property_name, $expected ) {
     2241                $user_query = new WP_User_Query();
     2242
     2243                $this->assertSame( $expected, $user_query->$property_name );
     2244        }
     2245
     2246        /**
     2247         * @ticket 58897
     2248         *
     2249         * @covers WP_User_Query::__get()
     2250         */
     2251        public function test_should_throw_deprecation_when_getting_dynamic_property() {
     2252                $user_query = new WP_User_Query();
     2253
     2254                $this->expectDeprecation();
     2255                $this->expectDeprecationMessage(
     2256                        'The property `undefined_property` is not declared. Getting a dynamic property is ' .
     2257                        'deprecated since version 6.4.0! Instead, declare the property on the class.'
     2258                );
     2259                $this->assertNull( $user_query->undefined_property, 'Getting a dynamic property should return null from WP_User_Query::__get()' );
     2260        }
     2261
     2262        /**
     2263         * @dataProvider data_compat_fields
     2264         * @ticket 58897
     2265         *
     2266         * @covers WP_User_Query::__set()
     2267         *
     2268         * @param string $property_name Property name to set.
     2269         */
     2270        public function test_should_set_compat_fields( $property_name ) {
     2271                $user_query = new WP_User_Query();
     2272                $value      = uniqid();
     2273
     2274                $user_query->$property_name = $value;
     2275                $this->assertSame( $value, $user_query->$property_name );
     2276        }
     2277
     2278        /**
     2279         * @ticket 58897
     2280         *
     2281         * @covers WP_User_Query::__set()
     2282         */
     2283        public function test_should_throw_deprecation_when_setting_dynamic_property() {
     2284                $user_query = new WP_User_Query();
     2285
     2286                $this->expectDeprecation();
     2287                $this->expectDeprecationMessage(
     2288                        'The property `undefined_property` is not declared. Setting a dynamic property is ' .
     2289                        'deprecated since version 6.4.0! Instead, declare the property on the class.'
     2290                );
     2291                $user_query->undefined_property = 'some value';
     2292        }
     2293
     2294        /**
     2295         * @dataProvider data_compat_fields
     2296         * @ticket 58897
     2297         *
     2298         * @covers WP_User_Query::__isset()
     2299         *
     2300         * @param string $property_name Property name to check.
     2301         * @param mixed $expected       Expected value.
     2302         */
     2303        public function test_should_isset_compat_fields( $property_name, $expected ) {
     2304                $user_query = new WP_User_Query();
     2305
     2306                $actual = isset( $user_query->$property_name );
     2307                if ( is_null( $expected ) ) {
     2308                        $this->assertFalse( $actual );
     2309                } else {
     2310                        $this->assertTrue( $actual );
     2311                }
     2312        }
     2313
     2314        /**
     2315         * @ticket 58897
     2316         *
     2317         * @covers WP_User_Query::__isset()
     2318         */
     2319        public function test_should_throw_deprecation_when_isset_of_dynamic_property() {
     2320                $user_query = new WP_User_Query();
     2321
     2322                $this->expectDeprecation();
     2323                $this->expectDeprecationMessage(
     2324                        'The property `undefined_property` is not declared. Checking `isset()` on a dynamic property ' .
     2325                        'is deprecated since version 6.4.0! Instead, declare the property on the class.'
     2326                );
     2327                $this->assertFalse( isset( $user_query->undefined_property ), 'Checking a dynamic property should return false from WP_User_Query::__isset()' );
     2328        }
     2329
     2330        /**
     2331         * @dataProvider data_compat_fields
     2332         * @ticket 58897
     2333         *
     2334         * @covers WP_User_Query::__unset()
     2335         *
     2336         * @param string $property_name Property name to unset.
     2337         */
     2338        public function test_should_unset_compat_fields( $property_name ) {
     2339                $user_query = new WP_User_Query();
     2340
     2341                unset( $user_query->$property_name );
     2342                $this->assertFalse( isset( $user_query->$property_name ) );
     2343        }
     2344
     2345        /**
     2346         * @ticket 58897
     2347         *
     2348         * @covers WP_User_Query::__unset()
     2349         */
     2350        public function test_should_throw_deprecation_when_unset_of_dynamic_property() {
     2351                $user_query = new WP_User_Query();
     2352
     2353                $this->expectDeprecation();
     2354                $this->expectDeprecationMessage(
     2355                        'A property `undefined_property` is not declared. Unsetting a dynamic property is ' .
     2356                        'deprecated since version 6.4.0! Instead, declare the property on the class.'
     2357                );
     2358                unset( $user_query->undefined_property );
     2359        }
     2360
     2361        /**
     2362         * Data provider.
     2363         *
     2364         * @return array
     2365         */
     2366        public function data_compat_fields() {
     2367                return array(
     2368                        'results'     => array(
     2369                                'property_name' => 'results',
     2370                                'expected'      => null,
     2371                        ),
     2372                        'total_users' => array(
     2373                                'property_name' => 'total_users',
     2374                                'expected'      => 0,
     2375                        ),
     2376                );
     2377        }
    22302378}
Note: See TracChangeset for help on using the changeset viewer.