140

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...
    }   
    
}
TylerH
  • 20,799
  • 66
  • 75
  • 101
SmxCde
  • 5,053
  • 7
  • 25
  • 45

5 Answers5

232

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...
  });

MikO
  • 18,243
  • 12
  • 77
  • 109
  • 2
    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)? – SmxCde Mar 10 '15 at 18:30
  • So far, the best solution I could come up with is `self::processSomething([__CLASS__, 'callback']);` – SmxCde Mar 10 '15 at 18:43
  • @SmxCde I updated the answer with all the options you have - If you are passing you `myStaticCallback` from a static method, you need to pass `__CLASS__`. If you are passing your `myCallBack` from a static method, you need to create a new instance of the class with `new MyClass()`, as within the static method there's no instance of `$this` you can pass... Hope it's all clear :) – MikO Mar 10 '15 at 20:59
  • 2
    You forgot to mention [`__invoke`](http://php.net/manual/en/language.oop5.magic.php#object.invoke) magic method. – Christian Apr 24 '16 at 12:04
  • Passing around method names as strings is a refactoring nightmare. – Kolyunya Oct 24 '16 at 09:24
  • `__CLASS__` for the static class methods section was what I was looking for. Thanks a million! – HartleySan May 28 '18 at 14:12
  • How do I pass params to that callback? – naneri Oct 02 '19 at 07:01
  • 7
    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` – luukvhoudt Apr 08 '20 at 19:41
  • since `self` is an alias for the class itself, can `[self::class, 'myCallback']` be used? – Electric Coffee Jul 30 '20 at 07:18
  • 4
    @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. – Ricardo Sep 08 '20 at 22:55
  • @luukvhoudt and for anyone with the same problem. I ended up using the PHP8.1 syntax from Dharman's answer below and it works flawlessly. – Link14 Dec 08 '22 at 16:45
34

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
16

Since 5.3 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();
});
Bankzilla
  • 2,086
  • 3
  • 25
  • 52
  • 10
    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... – MikO Mar 10 '15 at 01:00
  • 1
    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. – Bankzilla Mar 10 '15 at 01:17
  • 2
    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. – Kolyunya Oct 24 '16 at 09:32
  • 4
    Since PHP 5.6 you can use variadic function and argument unpacking: `$this->processSomething(function(...$args) { $this->myCallback(...$args); });` – Radek Pech May 15 '18 at 09:20
  • 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. – MannikJ Aug 08 '18 at 10:36
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...
}
Geovani Santos
  • 391
  • 1
  • 7
  • 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' ] – Yorick Mar 22 '23 at 20:04
0

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());
Dharman
  • 30,962
  • 25
  • 85
  • 135