Cherry pick #339 into release/9.2 (#50806)

* Make AccessiblePrivateMethods::_accessible_private_methods static.

This prevents the contents of the property to be included
in the object serialization.

* Add changelog file

* Small optimization

* Change 'self' to 'static' for consistency

* Add unit test for serialization

* Drop changelog

* Add changelog entry to readme.txt

---------

Co-authored-by: Nestor Soriano <konamiman@konamiman.com>
This commit is contained in:
Jorge A. Torres 2024-08-20 16:56:17 -03:00 committed by GitHub
parent 6937fc68a3
commit a9845e6a1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 85 additions and 8 deletions

View File

@ -173,6 +173,7 @@ WooCommerce comes with some sample data you can use to see how products look; im
**WooCommerce**
* Update - Turn AccessiblePrivateMethods::_accessible_private_methods into a static property. [#50806](https://github.com/woocommerce/woocommerce/pull/50806)
* Fix - Correct label of shipping dimensions length field. [#50180](https://github.com/woocommerce/woocommerce/pull/50180)
* Fix - Allow new accounts to set new password even if logged in. [#50700](https://github.com/woocommerce/woocommerce/pull/50700)
* Fix - Remove global_unique_id from interface and add warning in case it is not implemented [#50685](https://github.com/woocommerce/woocommerce/pull/50685)

View File

@ -2,7 +2,7 @@
namespace Automattic\WooCommerce\Internal\Traits;
use Automattic\WooCommerce\Utilities\ArrayUtil;
use SplObjectStorage;
/**
* This trait allows making private methods of a class accessible from outside.
@ -41,10 +41,11 @@ trait AccessiblePrivateMethods {
//phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore
/**
* List of instance methods marked as externally accessible.
* This is actually a dictionary where keys are object instances and values are arrays of method names.
*
* @var array
* @var SplObjectStorage
*/
private $_accessible_private_methods = array();
private static $_accessible_private_methods = null;
/**
* List of static methods marked as externally accessible.
@ -52,6 +53,13 @@ trait AccessiblePrivateMethods {
* @var array
*/
private static $_accessible_static_private_methods = array();
/**
* Flag indicating that $_accessible_private_methods already contains an entry for this object.
*
* @var bool
*/
private bool $_accessible_private_methods_is_initialized_for_this = false;
//phpcs:enable PSR2.Classes.PropertyDeclaration.Underscore
/**
@ -70,7 +78,7 @@ trait AccessiblePrivateMethods {
* @param int $accepted_args Optional. The number of arguments the function accepts. Default 1.
*/
protected static function add_action( string $hook_name, $callback, int $priority = 10, int $accepted_args = 1 ): void {
self::process_callback_before_hooking( $callback );
static::process_callback_before_hooking( $callback );
add_action( $hook_name, $callback, $priority, $accepted_args );
}
@ -90,7 +98,7 @@ trait AccessiblePrivateMethods {
* @param int $accepted_args Optional. The number of arguments the function accepts. Default 1.
*/
protected static function add_filter( string $hook_name, $callback, int $priority = 10, int $accepted_args = 1 ): void {
self::process_callback_before_hooking( $callback );
static::process_callback_before_hooking( $callback );
add_filter( $hook_name, $callback, $priority, $accepted_args );
}
@ -123,7 +131,15 @@ trait AccessiblePrivateMethods {
// Note that an "is_callable" check would be useless here:
// "is_callable" always returns true if the class implements __call.
if ( method_exists( $this, $method_name ) ) {
$this->_accessible_private_methods[ $method_name ] = $method_name;
if ( is_null( static::$_accessible_private_methods ) ) {
static::$_accessible_private_methods = new SplObjectStorage();
}
$methods = $this->_accessible_private_methods_is_initialized_for_this ? static::$_accessible_private_methods[ $this ] : array();
$methods[ $method_name ] = $method_name;
static::$_accessible_private_methods[ $this ] = $methods;
$this->_accessible_private_methods_is_initialized_for_this = true;
return true;
}
@ -154,7 +170,8 @@ trait AccessiblePrivateMethods {
* @throws \Error The called instance method doesn't exist or is private/protected and not marked as externally accessible.
*/
public function __call( $name, $arguments ) {
if ( isset( $this->_accessible_private_methods[ $name ] ) ) {
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped
if ( $this->_accessible_private_methods_is_initialized_for_this && isset( static::$_accessible_private_methods[ $this ][ $name ] ) ) {
return call_user_func_array( array( $this, $name ), $arguments );
} elseif ( is_callable( array( 'parent', '__call' ) ) ) {
return parent::__call( $name, $arguments );
@ -163,6 +180,7 @@ trait AccessiblePrivateMethods {
} else {
throw new \Error( 'Call to undefined method ' . get_class( $this ) . '::' . $name );
}
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
/**
@ -174,6 +192,7 @@ trait AccessiblePrivateMethods {
* @throws \Error The called static method doesn't exist or is private/protected and not marked as externally accessible.
*/
public static function __callStatic( $name, $arguments ) {
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped
if ( isset( static::$_accessible_static_private_methods[ $name ] ) ) {
return call_user_func_array( array( __CLASS__, $name ), $arguments );
} elseif ( is_callable( array( 'parent', '__callStatic' ) ) ) {
@ -186,5 +205,20 @@ trait AccessiblePrivateMethods {
} else {
throw new \Error( 'Call to undefined method ' . __CLASS__ . '::' . $name );
}
// phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
/**
* Class destructor, needed to remove this object from the dictionary of accessible instance methods.
*/
public function __destruct() {
if ( $this->_accessible_private_methods_is_initialized_for_this ) {
static::$_accessible_private_methods->detach( $this );
$this->_accessible_private_methods_is_initialized_for_this = false;
}
if ( is_callable( array( 'parent', '__destruct' ) ) ) {
parent::__destruct();
}
}
}

View File

@ -425,7 +425,23 @@ class AccessiblePrivateMethodsTest extends \WC_Unit_Test_Case {
$this->expectException( \Error::class );
$this->expectExceptionMessage( get_class( $sut ) . '::' . "$method_name can't be called statically, did you mean '$proper_method_name'?" );
$sut::$method_name( 'some_action', function() {} );
$sut::$method_name( 'some_action', function () {} );
}
/**
* @testdox The serialization of an object doesn't include information about the private methods made accessible.
*/
public function test_serialized_object_does_not_contain_accessible_methods_data() {
SerializableClass::init();
$instance = new SerializableClass();
$this->assertEquals( 'instance', $instance->private_instance_method() );
$this->assertEquals( 'static', $instance::private_static_method() );
//phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
$serialized = serialize( $instance );
$this->assertStringNotContainsString( 'private_instance_method', $serialized );
$this->assertStringNotContainsString( 'private_static_method', $serialized );
}
}
@ -449,4 +465,30 @@ class BaseClass {
//phpcs:enable Squiz.Commenting.FunctionComment.Missing
}
/**
* Class used to test serialization (instances of anonymous classes can't be serialized).
*/
class SerializableClass {
//phpcs:disable Squiz.Commenting.FunctionComment.Missing
use AccessiblePrivateMethods;
public function __construct() {
$this->mark_method_as_accessible( 'private_instance_method' );
}
//phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingInternalTag
final public static function init() {
self::mark_static_method_as_accessible( 'private_static_method' );
}
private function private_instance_method() {
return 'instance';
}
private static function private_static_method() {
return 'static';
}
//phpcs:enable Squiz.Commenting.FunctionComment.Missing
}
//phpcs:enable Generic.Files.OneObjectStructurePerFile.MultipleFound, Squiz.Classes.ClassFileName.NoMatch