mpen
mpen

Reputation: 282895

How to pass a reference to a callback function for use in array_filter with extra arguments?

The signature for my method looks like this:

public function ProgramRuleFilter(&$program, $today=null) {

When I invoke it like this,

$programs = array_filter($programs, array($this,'ProgramRuleFilter'));

Everything works as expected. The ProgramRuleFilter method updates the $program array and then returns true/false if it succeeded which correctly filters $programs.

However, now I want to pass an extra argument to the filter, $today. How can I do that?

I'm trying to invoke it like this:

$programs = array_filter($programs, new CallbackArgs(array($this,'ProgramRuleFilter'),$today));

Using this little class as a wrapper:

class CallbackArgs {
    private $callback;
    private $args;

    function __construct() {
        $args = func_get_args();
        $this->callback = array_shift($args);
        $this->args = $args;
    }

    function __invoke(&$arg) {
        return call_user_func_array($this->callback, array_merge(array($arg),$this->args));
    }
}

But the programs aren't being updated, so somewhere along the line it lost the reference to the original object. I'm not sure how to fix this.

Upvotes: 3

Views: 3781

Answers (2)

mpen
mpen

Reputation: 282895

I wrote a new method to handle it:

public static function array_filter_args($array, $callback) {
    $args = array_slice(func_get_args(),2);
    foreach($array as $key=>&$value) {
        if(!call_user_func_array($callback, array_merge(array(&$value),$args))) {
            unset($array[$key]);
        }
    }
    return $array;
}

Called like this:

$programs = ArrayHelper::array_filter_args($programs, array($this,'ProgramRuleFilter'), $today);

I didn't know you could do this array(&$value), but I thought I'd try it, and it looks like it works. I'm guessing that array_merge is the culprit that dereferences the variable otherwise.

Upvotes: 1

Jazz
Jazz

Reputation: 1485

The second argument to array_filter must be a callback; which means that array_filter itself will be calling your filter function. There is no way to tell array_filter to call that function in any other way, so you'll need to find a way to get the value of $today into your function some other way.

This is a perfect example of when to use a closure, which will let you bind some data (in this case, the value of $today) into a function / callback. Assuming you are using PHP 5.3 or later:

// Assuming $today has already been set

$object = $this; // PHP 5.4 eliminates the need for this
$programs = array_filter( $programs, function( $x ) use ( $today, $object ){
    return $object->ProgramRuleFilter( $x, $today );
});

This defines a closure inline, using the values of $today and $object from the parent scope, and then just calls your existing function ProgramRuleFilter on that $object. (The somewhat unusual $object = $this gets around the fact that otherwise, the closure would not be able to call a method on your object instance. But in PHP 5.4, you can replace $object with $this inside the closure.)

Now, this is a somewhat inelegant way to do it, because all this closure does is hand off the work to the ProgramRuleFilter function. A better way would be to use the closure instead of the function. So:

// Assuming $today has already been set

$filter = function( $x ) use ( $today ){
    // Cut and paste the contents of ProgramRuleFilter() here,
    // and make it operate on $x and $today
};
$programs = array_filter( $programs, $filter );

Which variation works best for you will depend on the implementation of the rest of your app. Good luck!

Upvotes: 10

Related Questions