155

I have a class with methods that I want to use as callbacks.
How can I pass them as arguments?

Class MyClass {
    
    public function myMethod() {
        // How should these be called?
        $this->processSomething(this->myCallback);
        $this->processSomething(self::myStaticCallback);
    }

    private function processSomething(callable $callback) {
        // Process something...
        $callback();
    }

    private function myCallback() {
        // Do something...
    }

    private static function myStaticCallback() {
        // Do something...
    }   
    
}
0

5 Answers 5

252

Check the callable manual to see all the different ways to pass a function as a callback. I copied that manual here and added some examples of each approach based on your scenario.

Callable


  • A PHP function is passed by its name as a string. Any built-in or user-defined function can be used, except language constructs such as: array(), echo, empty(), eval(), exit(), isset(), list(), print or unset().
  // Not applicable in your scenario
  $this->processSomething('some_global_php_function');

  • A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1.
  // Only from inside the same class
  $this->processSomething([$this, 'myCallback']);
  $this->processSomething([$this, 'myStaticCallback']);
  // From either inside or outside the same class
  $myObject->processSomething([new MyClass(), 'myCallback']);
  $myObject->processSomething([new MyClass(), 'myStaticCallback']);

  • Static class methods can also be passed without instantiating an object of that class by passing the class name instead of an object at index 0.
  // Only from inside the same class
  $this->processSomething([__CLASS__, 'myStaticCallback']);
  // From either inside or outside the same class
  $myObject->processSomething(['\Namespace\MyClass', 'myStaticCallback']);
  $myObject->processSomething(['\Namespace\MyClass::myStaticCallback']); // PHP 5.2.3+
  $myObject->processSomething([MyClass::class, 'myStaticCallback']); // PHP 5.5.0+

  • Apart from common user-defined function, anonymous functions can also be passed to a callback parameter.
  // Not applicable in your scenario unless you modify the structure
  $this->processSomething(function() {
      // process something directly here...
  });

Sign up to request clarification or add additional context in comments.

13 Comments

MikO, thanks for your response! I know that this wasn't in original question, but how to call for static method with static method as argument from static method (where there is no $this)?
So far, the best solution I could come up with is self::processSomething([__CLASS__, 'callback']);
You forgot to mention __invoke magic method.
Well, I guess I'm the only one who gets a Fatal error: Uncaught TypeError: Argument 1 passed to ::processSomething must be callable, array given
@Fleuv, that’s because you are passing an out of scope method (a protected or private method to an external function or class). Not an obvious error message at all.
|
49

As of PHP 8.1, we now have first-class callables. They use the syntax $callable = functionName(...). The three dots are part of the syntax and not an omission.

You can use the new syntax to create callable class methods.

Class MyClass {
    
    public function myMethod() {
        // first-class callables
        $this->processSomething($this->myCallback(...));
        $this->processSomething(self::myStaticCallback(...));
    }

    private function processSomething(callable $callback) {
        // Process something...
        $callback();
    }

    private function myCallback() {
        // Do something...
    }

    private static function myStaticCallback() {
        // Do something...
    }   
    
}

The three dots are not an omission/placeholder for parameters. They are a special syntax for creating a callable. If the method accepts no parameters, the syntax remains the same.

Comments

17

Since 5.4 there is a more elegant way you can write it, I'm still trying to find out if it can be reduced more

$this->processSomething(function() {
    $this->myCallback();
});

Since 8.1 PHP has the first class callable syntax:

$this->processSomething(
    $this->myCallback(...);
);

5 Comments

This may be more elegant, but you're adding overhead by adding an unnecesary call to the anonymous function, which just calls the actual callback...And what if myCallback needs 5 arguments? You have to write those arguments 4 times in that class! I agree anonymous functions are nice as callbacks, but in the case you write the code inside the anonymous function, not just including a call to another function...
You're correct, I didn't think about passing the arguments across. But unless I'm reading it wrong, the way you've specified you also can't pass arguements.
Upvoting this. And yes, it can be even more elegant: $this->processSomething($this->myCallback()); while myCallback() should return an anonymous function. This is WAY more maintainable than passing around method names as strings.
Since PHP 5.6 you can use variadic function and argument unpacking: $this->processSomething(function(...$args) { $this->myCallback(...$args); });
Is there not a better way of passing an existing function? Just another example why I don't like to do it like this: Assume processSomething validates the given parameter(s) and throws an Exception if the first paramater is not a valid callback. If you wrap the call in a new Closure like above, it will first go through and then blow up later when the callback function is actually executed. That makes it very hard to debug, because you're skipping the validation which was implemented for a reason.
14

You can also to use call_user_func() to specify a callback:

public function myMethod() {
    call_user_func(array($this, 'myCallback'));
}

private function myCallback() {
    // do something...
}

1 Comment

Thank you - this helped me with a puzzle while adding a custom tab to a WooCommerce product page tabs set. The array included a callback and without really thinking I was trying to call the function using 'callback' => $this->tab_content() and it wasn't working at all. On digging into the WooCommerce code, I saw that that it was doing call_user_func() on the callback and on seeing your suggestion, i realised it needed to be an array, ie 'callback' => [ $this, 'tab_content' ]
-1

You can set the method return type to callable. It works for PHP 7.1

protected function myMethod(): callable
{
    return function (int $j) {
    };
}

Then call it like this:

someFunction($this->myMethod());

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.