Make WordPress Core

Opened 5 years ago

Last modified 5 years ago

#45374 new defect (bug)

apply_filters_ref_array() no longer passes arguments by reference

Reported by: johnbillion's profile johnbillion Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: 4.7
Component: Plugins Keywords: needs-patch needs-unit-tests
Focuses: Cc:

Description

It appears that since the introduction of the WP_Hook class in WordPress 4.7 (#17817), apply_filters_ref_array() no longer passes its arguments by reference.

Example:

add_filter( 'posts_clauses', function( $clauses, &$query ) {
	return $clauses;
}, 10, 2 );

The above code will trigger the following PHP warning:

Parameter 2 to {closure}() expected to be a reference, value given

cc @jbrinley FYI

Change History (8)

#1 @jbrinley
5 years ago

This didn't change with 4.7. From the pre-4.7 do_action_ref_array(), which hasn't changed much since 2.2 (see #3875):

call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));

Here's a simplified example to show that we get the same warning: https://3v4l.org/qeUZt

<?php
function do_action_ref_array( $callback, $args ) {
    call_user_func_array( $callback, array_slice( $args, 0, 2 ) );
}

$function = function( $arg1, &$arg2 ) {
    return $arg1;
};

$args = [1, 2, 3];

do_action_ref_array( $function, $args );

Before 2.2, you could use call-time pass-by-reference to pass your reference through to the callbacks, but that's not supported in modern PHP.

#2 @johnbillion
5 years ago

#45480 was marked as a duplicate.

#3 @henry.wright
5 years ago

The example given doesn't attempt to pass a reference at call time. It is trying to pass values. They can be references according to modern PHP. See https://3v4l.org/AWr5p

Last edited 5 years ago by henry.wright (previous) (diff)

#4 @henry.wright
5 years ago

Taken from the manual entry for call_user_func_array()

Before PHP 5.4, referenced variables in param_arr are passed to the function by reference, regardless of whether the function expects the respective parameter to be passed by reference. This form of call-time pass by reference does not emit a deprecation notice, but it is nonetheless deprecated, and has been removed in PHP 5.4

Ref http://php.net/manual/en/function.call-user-func-array.php

#5 @websupporter
5 years ago

Since its almost end of the work day, I leave what I found so far.

This is the code I've tested with

<?php
function testFilter( $text, &$object ) {
    return $text;
}
add_filter( 'testFilter', 'testFilter', 10, 2 );

function testAction( $text, &$object ) {
    return;
}
add_action( 'testAction', 'testAction', 10, 2 );

class Test {

    public function run() {
        // Somehow the &$this does not work.
        apply_filters_ref_array('testFilter', ['test', &$this]);
        apply_filters_ref_array('testFilter', array('Test', $this));
        
        // It seems to be limited to filter.
        do_action_ref_array('testAction', array('test', &$this));
    }
}
(new Test())->run();

$object = new Test();
apply_filters_ref_array('testFilter', ['test', $object]);
/**
 * This does not throw.
 */
apply_filters_ref_array('testFilter', ['test', &$object]);

It seems to be related to calls where the referenced object is $this. The PHP version I've tested is 7.2.12-1.

It looks to me, it has something to do with the reassignment of the first value of the $args here https://core.trac.wordpress.org/browser/tags/4.9.8/src/wp-includes/class-wp-hook.php#L279

When I just do not run this assignment, the problem disappears.

But, at least for PHP 7.1.25 the problem seems to be a bit more: https://3v4l.org/DCCeh

As you can see, here $this is not referenced, although I do not change the $args. Also noteworthy, in this example I can change $args[0] without having another problem (except for the PHP 7.1.25).

Edit: I edited my code example for clarification, but it didn't change my outcomes.

Last edited 5 years ago by websupporter (previous) (diff)

#6 @henry.wright
5 years ago

@websupporter

I've noticed an issue with references and call_user_func_array().

Example 1
This uses call_user_func_array() to pass by reference: https://3v4l.org/JDcqk

Example 2
This uses a function call to pass by reference: https://3v4l.org/sQFsU

#7 @henry.wright
5 years ago

To add to my last comment

Related to example 1, if we prefix the array elements with &, we see call_user_func_array() doesn't cause problems: https://3v4l.org/ZfMrf

@websupporter I think you are right to think the $args[ 0 ] = $value; assignment is the issue. After this point $args[0] is not a reference. Hence we get the warning shown in example 1.

Note: See TracTickets for help on using tickets.