Skip to content

Commit 9560811

Browse files
committed
Plugins: Improve hook performance by using spl_object_id() instead of spl_object_hash() to construct unique IDs.
* Also use `spl_object_id()` similarly when registering and unregistering classic widgets. * Improve typing and phpdoc in `_wp_filter_build_unique_id()`. Return `null` for malformed callbacks. * Add tests for `_wp_filter_build_unique_id()`. * Improve type safety of `WP_Hook::add_filter()` in case an invalid callback is provided for parity with `::has_filter()` and `::remove_filter()`. Developed in WordPress#11865 Follow-up to r46220, r46801, r60179. Props bor0, westonruter, SergeyBiryukov, schlessera, arshidkv12, knutsp, spacedmonkey, swissspidy. See #64898. Fixes #58291. git-svn-id: https://develop.svn.wordpress.org/trunk@62408 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 4e28109 commit 9560811

4 files changed

Lines changed: 84 additions & 16 deletions

File tree

src/wp-includes/class-wp-hook.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ public function add_filter( $hook_name, $callback, $priority, $accepted_args ) {
8585
}
8686

8787
$idx = _wp_filter_build_unique_id( $hook_name, $callback, $priority );
88+
if ( null === $idx ) {
89+
return;
90+
}
8891

8992
$priority_existed = isset( $this->callbacks[ $priority ] );
9093

src/wp-includes/class-wp-widget-factory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function WP_Widget_Factory() {
5757
*/
5858
public function register( $widget ) {
5959
if ( $widget instanceof WP_Widget ) {
60-
$this->widgets[ spl_object_hash( $widget ) ] = $widget;
60+
$this->widgets[ spl_object_id( $widget ) ] = $widget;
6161
} else {
6262
$this->widgets[ $widget ] = new $widget();
6363
}
@@ -74,7 +74,7 @@ public function register( $widget ) {
7474
*/
7575
public function unregister( $widget ) {
7676
if ( $widget instanceof WP_Widget ) {
77-
unset( $this->widgets[ spl_object_hash( $widget ) ] );
77+
unset( $this->widgets[ spl_object_id( $widget ) ] );
7878
} else {
7979
unset( $this->widgets[ $widget ] );
8080
}

src/wp-includes/plugin.php

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -986,33 +986,36 @@ function _wp_call_all_hook( $args ) {
986986
* @since 2.2.3
987987
* @since 5.3.0 Removed workarounds for spl_object_hash().
988988
* `$hook_name` and `$priority` are no longer used,
989-
* and the function always returns a string.
989+
* and no longer returns false, but can still return void for invalid callbacks.
990+
* @since 6.9.0 Returns explicit null if an invalid callback is supplied.
991+
* @since 7.1.0 Uses spl_object_id() instead of spl_object_hash() for performance.
990992
*
991993
* @access private
992994
*
993-
* @param string $hook_name Unused. The name of the filter to build ID for.
994-
* @param callable|string|array $callback The callback to generate ID for. The callback may
995-
* or may not exist.
996-
* @param int $priority Unused. The order in which the functions
997-
* associated with a particular action are executed.
998-
* @return string|null Unique function ID for usage as array key.
999-
* Null if a valid `$callback` is not passed.
995+
* @param string $hook_name Unused. The name of the filter to build ID for.
996+
* @param callable $callback The callback to generate ID for. The callback may
997+
* or may not exist.
998+
* @param int $priority Unused. The order in which the functions
999+
* associated with a particular action are executed.
1000+
* @return string|null Unique function ID for usage as array key, or null if it couldn't be determined.
10001001
*/
1001-
function _wp_filter_build_unique_id( $hook_name, $callback, $priority ) {
1002+
function _wp_filter_build_unique_id( $hook_name, $callback, $priority ): ?string {
10021003
if ( is_string( $callback ) ) {
10031004
return $callback;
10041005
}
10051006

10061007
if ( is_object( $callback ) ) {
1007-
// Closures are currently implemented as objects.
1008-
$callback = array( $callback, '' );
1009-
} else {
1010-
$callback = (array) $callback;
1008+
return (string) spl_object_id( (object) $callback );
1009+
}
1010+
1011+
$callback = (array) $callback;
1012+
if ( ! isset( $callback[1] ) || ! is_string( $callback[1] ) ) {
1013+
return null;
10111014
}
10121015

10131016
if ( is_object( $callback[0] ) ) {
10141017
// Object class calling.
1015-
return spl_object_hash( $callback[0] ) . $callback[1];
1018+
return ( (string) spl_object_id( $callback[0] ) ) . $callback[1];
10161019
} elseif ( is_string( $callback[0] ) ) {
10171020
// Static calling.
10181021
return $callback[0] . '::' . $callback[1];
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/**
4+
* Tests for _wp_filter_build_unique_id().
5+
*
6+
* @group hooks
7+
* @covers ::_wp_filter_build_unique_id
8+
*/
9+
class Tests_Hooks_BuildUniqueId extends WP_UnitTestCase {
10+
11+
public function test_string_callback_returns_string(): void {
12+
$result = _wp_filter_build_unique_id( '', '__return_null', 10 );
13+
$this->assertIsString( $result );
14+
$this->assertSame( '__return_null', $result );
15+
}
16+
17+
public function test_closure_returns_string(): void {
18+
$cb = function (): void {};
19+
$result = _wp_filter_build_unique_id( '', $cb, 10 );
20+
$this->assertIsString( $result );
21+
}
22+
23+
public function test_object_callback_returns_string(): void {
24+
$a = new MockAction();
25+
$result = _wp_filter_build_unique_id( '', array( $a, 'action' ), 10 );
26+
$this->assertIsString( $result );
27+
}
28+
29+
public function test_static_callback_returns_string(): void {
30+
$result = _wp_filter_build_unique_id( '', array( 'MockAction', 'action' ), 10 );
31+
$this->assertIsString( $result );
32+
}
33+
34+
public function test_two_different_objects_produce_different_ids(): void {
35+
$a = new MockAction();
36+
$b = new MockAction();
37+
$this->assertNotSame(
38+
_wp_filter_build_unique_id( '', array( $a, 'action' ), 10 ),
39+
_wp_filter_build_unique_id( '', array( $b, 'action' ), 10 )
40+
);
41+
}
42+
43+
public function test_same_object_produces_same_id(): void {
44+
$a = new MockAction();
45+
$this->assertSame(
46+
_wp_filter_build_unique_id( '', array( $a, 'action' ), 10 ),
47+
_wp_filter_build_unique_id( '', array( $a, 'action' ), 10 )
48+
);
49+
}
50+
51+
public function test_malformed_array_missing_method_returns_null(): void {
52+
$a = new MockAction();
53+
$result = _wp_filter_build_unique_id( '', array( $a ), 10 );
54+
$this->assertNull( $result );
55+
}
56+
57+
public function test_malformed_array_non_string_method_returns_null(): void {
58+
$a = new MockAction();
59+
$result = _wp_filter_build_unique_id( '', array( $a, 123 ), 10 );
60+
$this->assertNull( $result );
61+
}
62+
}

0 commit comments

Comments
 (0)